├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── codecov.yaml ├── dependabot.yml ├── linters │ └── .yaml-lint.yaml └── workflows │ ├── auto-merge.yml │ ├── base-linting.yaml │ ├── ci.yaml │ ├── publish-docs.yaml │ └── publish.yaml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── docs ├── _charts │ ├── _template.md │ ├── adjacency_matrix.md │ ├── arc_diagram.md │ ├── arc_flow.md │ ├── bar.md │ ├── big_number.md │ ├── boxplot.md │ ├── candlestick.md │ ├── choropleth.md │ ├── connected_scatterplot.md │ ├── correlation_matrix.md │ ├── cumulative.md │ ├── dashboards.md │ ├── dataprism.md │ ├── dendrogram.md │ ├── dimension_matrix.md │ ├── diverging_bar.md │ ├── donut.md │ ├── dotplot.md │ ├── flow_diagram.md │ ├── force_directed_network.md │ ├── funnel.md │ ├── funnel_chart.md │ ├── funnel_sunburst.md │ ├── funnel_tree.md │ ├── heatmap.md │ ├── hexbin.md │ ├── histogram.md │ ├── horizon.md │ ├── line.md │ ├── line_percent.md │ ├── parallel_coordinates.md │ ├── partition.md │ ├── pie.md │ ├── radar_plot.md │ ├── ridgeline.md │ ├── sankey.md │ ├── scatter.md │ ├── scatterplot_matrix.md │ ├── stacked_area.md │ ├── stacked_percent.md │ ├── sunburst.md │ ├── tilemap.md │ ├── treemap.md │ ├── violin.md │ └── wordcloud.md ├── index.md ├── plotting │ ├── basic_charts.md │ ├── comparison_charts.md │ ├── conditional_formatting.md │ ├── dashboards.md │ ├── faceting.md │ ├── funnel_charts.md │ ├── maps.md │ ├── overview.md │ ├── part_to_whole_charts.md │ ├── relationship_charts.md │ ├── summary_charts.md │ └── time_series_charts.md ├── reference │ ├── charts │ │ ├── basic_charts.md │ │ ├── comparison_charts.md │ │ ├── dashboards.md │ │ ├── funnel_charts.md │ │ ├── maps.md │ │ ├── part_to_whole_charts.md │ │ ├── relationship_charts.md │ │ ├── summary_charts.md │ │ └── time_series_charts.md │ └── datatypes │ │ ├── compatibility.md │ │ ├── dataframes.md │ │ ├── datetime.md │ │ ├── geometry.md │ │ ├── misc.md │ │ ├── numeric.md │ │ ├── overview.md │ │ └── text.md └── screenshots │ ├── attrs_view1.png │ ├── dashboard_custom1.png │ ├── dashboard_simple1.png │ ├── dashboard_views1.png │ ├── dx_display_sample1.png │ ├── dx_display_sample2.png │ ├── dx_display_sample3.png │ ├── dx_plain_mode_sample1.png │ ├── dx_settings_sample1.png │ ├── dx_settings_sample2.png │ ├── dx_settings_sample3.png │ ├── dx_simple_mode_sample1.png │ ├── plotting_bar_custom1.png │ ├── plotting_bar_custom1_pd.png │ ├── plotting_bar_simple1.png │ ├── plotting_bar_simple1_pd.png │ ├── plotting_line_custom1.png │ ├── plotting_line_custom1_pd.png │ ├── plotting_line_simple1.png │ ├── plotting_line_simple1_pd.png │ ├── plotting_pie_custom1.png │ ├── plotting_pie_custom1_pd.png │ ├── plotting_pie_simple1.png │ ├── plotting_pie_simple1_pd.png │ ├── plotting_scatter_custom1.png │ ├── plotting_scatter_custom1_pd.png │ ├── plotting_scatter_simple1.png │ ├── plotting_scatter_simple1_pd.png │ ├── plotting_tilemap_custom1.png │ ├── plotting_tilemap_custom1_pd.png │ ├── plotting_tilemap_simple1.png │ ├── plotting_tilemap_simple1_pd.png │ ├── plotting_violin_custom1.png │ ├── plotting_violin_custom1_pd.png │ ├── plotting_violin_simple1.png │ ├── plotting_violin_simple1_pd.png │ ├── plotting_wordcloud_custom1.png │ ├── plotting_wordcloud_custom1_pd.png │ ├── plotting_wordcloud_simple1.png │ └── plotting_wordcloud_simple1_pd.png ├── mkdocs.yml ├── noxfile.py ├── poetry.lock ├── poetry.toml ├── pyproject.toml ├── pytest.ini ├── requirements-docs.txt ├── setup.cfg ├── src └── dx │ ├── __init__.py │ ├── comms │ ├── __init__.py │ ├── assignment.py │ └── resample.py │ ├── datatypes │ ├── __init__.py │ ├── compatibility.py │ ├── date_time.py │ ├── geometry.py │ ├── main.py │ ├── misc.py │ ├── numeric.py │ └── text.py │ ├── dependencies.py │ ├── dx.py │ ├── filtering.py │ ├── formatters │ ├── __init__.py │ ├── enhanced.py │ ├── main.py │ ├── plain.py │ ├── simple.py │ └── summarizing.py │ ├── loggers.py │ ├── plotting │ ├── __init__.py │ ├── dashboards.py │ ├── dex │ │ ├── __init__.py │ │ ├── basic_charts.py │ │ ├── comparison_charts.py │ │ ├── funnel_charts.py │ │ ├── map_charts.py │ │ ├── part_to_whole_charts.py │ │ ├── relationship_charts.py │ │ ├── summary_charts.py │ │ └── time_series_charts.py │ ├── main.py │ └── utils.py │ ├── sampling.py │ ├── settings.py │ ├── types │ ├── __init__.py │ ├── charts │ │ ├── __init__.py │ │ ├── _base.py │ │ ├── _template.py │ │ ├── adjacency_matrix.py │ │ ├── arc_flow.py │ │ ├── bar.py │ │ ├── bignumber.py │ │ ├── candlestick.py │ │ ├── choropleth.py │ │ ├── connected_scatter.py │ │ ├── correlation_matrix.py │ │ ├── cumulative.py │ │ ├── dataprism.py │ │ ├── dendrogram.py │ │ ├── dimension_matrix.py │ │ ├── diverging_bar.py │ │ ├── donut.py │ │ ├── dotplot.py │ │ ├── flow_diagram.py │ │ ├── force_directed_network.py │ │ ├── funnel.py │ │ ├── funnel_chart.py │ │ ├── funnel_sunburst.py │ │ ├── funnel_tree.py │ │ ├── grid.py │ │ ├── hexbin.py │ │ ├── line.py │ │ ├── line_percent.py │ │ ├── options.py │ │ ├── parcoords.py │ │ ├── partition.py │ │ ├── pie.py │ │ ├── sankey.py │ │ ├── scatter.py │ │ ├── scatterplot_matrix.py │ │ ├── stacked_area.py │ │ ├── stacked_percent.py │ │ ├── summary.py │ │ ├── sunburst.py │ │ ├── tilemap.py │ │ ├── treemap.py │ │ └── wordcloud.py │ ├── dex_metadata.py │ ├── filters.py │ └── main.py │ └── utils │ ├── __init__.py │ ├── formatting.py │ └── tracking.py ├── tests ├── conftest.py ├── test_comms.py ├── test_dataresource.py ├── test_datatype_handling.py ├── test_datatypes.py ├── test_dex_charts.py ├── test_dx.py ├── test_formatting.py ├── test_metadata.py ├── test_registering.py ├── test_renderable_types.py ├── test_resampling.py ├── test_sampling.py ├── test_settings.py ├── test_tracking.py └── test_utils.py └── tox.ini /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Environment (please complete the following information):** 27 | - OS: [e.g. macOS, Linux, WSL] 28 | - Python Version: [e.g. 3.8.2] 29 | - Library Version: [e.g. 1.1.0] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/codecov.yaml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: true 3 | 4 | coverage: 5 | precision: 2 6 | round: down 7 | range: "70...100" 8 | 9 | parsers: 10 | gcov: 11 | branch_detection: 12 | conditional: true 13 | loop: true 14 | method: false 15 | macro: false 16 | 17 | comment: 18 | layout: "reach,diff,flags,files,footer" 19 | behavior: default 20 | require_changes: false 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "pip" 7 | directory: "/" # Location of package manifests 8 | schedule: 9 | interval: "daily" 10 | 11 | - package-ecosystem: "github-actions" 12 | # Workflow files stored in the 13 | # default location of `.github/workflows` 14 | directory: "/" 15 | schedule: 16 | interval: "daily" 17 | -------------------------------------------------------------------------------- /.github/linters/.yaml-lint.yaml: -------------------------------------------------------------------------------- 1 | ########################################### 2 | # These are the rules used for # 3 | # linting all the yaml files in the stack # 4 | # NOTE: # 5 | # You can disable line with: # 6 | # # yamllint disable-line # 7 | ########################################### 8 | extends: default 9 | 10 | rules: 11 | colons: 12 | max-spaces-before: 0 13 | max-spaces-after: 1 14 | 15 | commas: 16 | max-spaces-before: 0 17 | min-spaces-after: 1 18 | max-spaces-after: 1 19 | 20 | comments: disable 21 | 22 | comments-indentation: disable 23 | 24 | document-end: disable 25 | 26 | document-start: 27 | present: false 28 | 29 | empty-lines: 30 | max: 2 31 | max-start: 0 32 | max-end: 0 33 | 34 | hyphens: 35 | max-spaces-after: 1 36 | 37 | indentation: 38 | spaces: consistent 39 | indent-sequences: true 40 | check-multi-line-strings: false 41 | 42 | line-length: 43 | max: 160 44 | 45 | new-line-at-end-of-file: enable 46 | 47 | new-lines: 48 | type: unix 49 | -------------------------------------------------------------------------------- /.github/workflows/auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | on: pull_request_target # yamllint disable-line rule:truthy 3 | 4 | permissions: 5 | pull-requests: write 6 | contents: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.actor == 'dependabot[bot]' }} 12 | steps: 13 | - name: Dependabot metadata 14 | id: metadata 15 | uses: dependabot/fetch-metadata@v1.5.1 16 | with: 17 | github-token: "${{ secrets.GITHUB_TOKEN }}" 18 | - name: Enable auto-merge for Dependabot PRs 19 | run: gh pr merge --auto --squash "$PR_URL" 20 | env: 21 | PR_URL: ${{github.event.pull_request.html_url}} 22 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 23 | -------------------------------------------------------------------------------- /.github/workflows/base-linting.yaml: -------------------------------------------------------------------------------- 1 | name: Base Linting 2 | 3 | # Trigger the workflow on all pull requests and only pushes to the main branch 4 | # yamllint disable-line rule:truthy 5 | on: 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | base-linting: 12 | name: base-linting 13 | runs-on: ubuntu-20.04 14 | 15 | steps: 16 | - name: Checkout Code 17 | uses: actions/checkout@v3 18 | 19 | - name: Lint Dockerfile, Shell scripts, YAML 20 | uses: github/super-linter@v5 21 | env: 22 | DEFAULT_BRANCH: main 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | 25 | # Linters to enable 26 | VALIDATE_BASH: true 27 | VALIDATE_BASH_EXEC: true 28 | VALIDATE_YAML: true 29 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: # yamllint disable-line rule:truthy 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-20.04 14 | strategy: 15 | matrix: 16 | python_version: ["3.9", "3.10"] 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v3 20 | - name: Setup nox 21 | uses: daisylb/setup-nox@v2.1.0 22 | - name: Setup poetry 23 | uses: Gr1N/setup-poetry@v8 24 | with: 25 | poetry-version: "1.5.1" 26 | - name: Install dependencies 27 | run: | 28 | poetry export -f requirements.txt --extras "docs" --without-hashes --with dev --output requirements.txt 29 | pip install -r requirements.txt 30 | - name: Run tests 31 | run: nox -s test-${{ matrix.python_version }} 32 | - name: Generate coverage XML 33 | run: nox -s generate_coverage_xml 34 | - name: Upload coverage to Codecov 35 | uses: codecov/codecov-action@v3 36 | lint: 37 | runs-on: ubuntu-20.04 38 | steps: 39 | - name: Checkout code 40 | uses: actions/checkout@v3 41 | - name: Setup nox 42 | uses: daisylb/setup-nox@v2.1.0 43 | - name: Setup poetry 44 | uses: Gr1N/setup-poetry@v8 45 | with: 46 | poetry-version: "1.4.1" 47 | - name: Install dependencies 48 | run: | 49 | poetry export -f requirements.txt --extras "docs" --without-hashes --with dev --output requirements.txt 50 | pip install -r requirements.txt 51 | - name: Run lint 52 | run: nox -s lint 53 | -------------------------------------------------------------------------------- /.github/workflows/publish-docs.yaml: -------------------------------------------------------------------------------- 1 | name: Publish docs 2 | 3 | on: # yamllint disable-line rule:truthy 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | permissions: 12 | contents: write 13 | 14 | jobs: 15 | deploy: 16 | runs-on: ubuntu-20.04 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: actions/setup-python@v4 20 | with: 21 | python-version: 3.9 22 | - run: pip install -r requirements-docs.txt 23 | - run: mkdocs gh-deploy --force 24 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | 13 | - name: Install Poetry 14 | shell: bash 15 | run: curl -sSL https://install.python-poetry.org | POETRY_VERSION=1.3.2 python3 - 16 | 17 | - name: Set up Python 18 | uses: actions/setup-python@v4 19 | with: 20 | python-version: "3.9" 21 | 22 | - run: poetry publish --build -u __token__ -p ${{ secrets.PYPI_TOKEN }} 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | venv/ 48 | .venv/ 49 | .python-version 50 | .pytest_cache 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | 59 | # Sphinx documentation 60 | docs/_build/ 61 | 62 | # mkdocs 63 | site/ 64 | 65 | # PyBuilder 66 | target/ 67 | 68 | #Ipython Notebook 69 | .ipynb_checkpoints 70 | 71 | # intellij 72 | .idea/ 73 | 74 | tmp/ 75 | .cache_ggshield 76 | 77 | # VSCode settings 78 | .vscode 79 | vscode/ 80 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via a GitHub issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a [code of conduct](./CODE_OF_CONDUCT.md), please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | Before submitting a pull request: 11 | 12 | 1. Ensure tests pass for bug fixes. 13 | 2. Ensure new tests are added to cover new features or non trivial changes. 14 | 3. Ensure relevant documentation is updated. 15 | 16 | ## Environment Setup 17 | 18 | Install the following: 19 | 20 | * [Poetry](https://python-poetry.org/docs/#installation) 21 | * GDAL 22 | * macOS: `brew install gdal` 23 | * Debian/Ubuntu: `sudo apt-get install libgdal-dev` 24 | 25 | Run the following 26 | 27 | 1. Clone the repository: 28 | 29 | $ git clone git@github.com:noteable-io/dx.git && cd dx 30 | 31 | 2. Install dependencies: 32 | 33 | $ poetry install 34 | 35 | 3. Validate the environment by running the tests: 36 | 37 | $ poetry run pytest 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Noteable Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /docs/_charts/_template.md: -------------------------------------------------------------------------------- 1 | 2 | ## Chart Name 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Chart Name 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/adjacency_matrix.md: -------------------------------------------------------------------------------- 1 | 2 | ## Adjacency Matrix 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Adjacency Matrix 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/arc_diagram.md: -------------------------------------------------------------------------------- 1 | 2 | ## Arc Diagram 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Arc Diagram 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/arc_flow.md: -------------------------------------------------------------------------------- 1 | 2 | ## Arc Flow 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Arc Flow 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/bar.md: -------------------------------------------------------------------------------- 1 | 2 | ## [Bar](../../reference/charts/basic_charts/#src.dx.plotting.dex.basic_charts.bar) 3 | ### Simple 4 | === "dx" 5 | 6 | ```python 7 | dx.bar(df, x='keyword_column', y='integer_column') 8 | ``` 9 | ![](../screenshots/plotting_bar_simple1.png) 10 | 11 | === "pd.options.plotting.backend = 'dx'" 12 | 13 | !!! info "Make sure you [enable `dx` as a pandas plotting backend](../plotting/overview.md#enabling-pandas-plotting-backend) first." 14 | 15 | ```python 16 | df.plot.bar(x='keyword_column', y='integer_column') 17 | ``` 18 | ![](../screenshots/plotting_bar_simple1_pd.png) 19 | 20 | ### Customized 21 | 22 | === "dx" 23 | 24 | ```python 25 | dx.bar( 26 | df, 27 | x='keyword_column', 28 | y='integer_column', 29 | y2='float_column', 30 | y2_style='dot', 31 | horizontal=True, 32 | bar_width='index', 33 | group_other=True, 34 | column_sort_order="desc", 35 | column_sort_type="string", 36 | pro_bar_mode="combined", 37 | combination_mode="max", 38 | show_bar_labels=True, 39 | ) 40 | ``` 41 | ![](../screenshots/plotting_bar_custom1.png) 42 | 43 | === "pd.options.plotting.backend = 'dx'" 44 | 45 | !!! info "Make sure you [enable `dx` as a pandas plotting backend](../plotting/overview.md#enabling-pandas-plotting-backend) first." 46 | 47 | ```python 48 | df.plot.bar( 49 | x='keyword_column', 50 | y='integer_column', 51 | y2='float_column', 52 | y2_style='dot', 53 | horizontal=True, 54 | bar_width='index', 55 | group_other=True, 56 | column_sort_order="desc", 57 | column_sort_type="string", 58 | pro_bar_mode="combined", 59 | combination_mode="max", 60 | show_bar_labels=True, 61 | ) 62 | ``` 63 | ![](../screenshots/plotting_bar_custom1_pd.png) 64 | 65 | 66 | 67 | ## [Bar](../../../plotting/basic_charts/#bar) 68 | ::: src.dx.plotting.dex.basic_charts.bar 69 | -------------------------------------------------------------------------------- /docs/_charts/big_number.md: -------------------------------------------------------------------------------- 1 | 2 | ## Big Number 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Big Number 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/boxplot.md: -------------------------------------------------------------------------------- 1 | 2 | ## Boxplot 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Boxplot 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/candlestick.md: -------------------------------------------------------------------------------- 1 | 2 | ## Candlestick 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Candlestick 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/choropleth.md: -------------------------------------------------------------------------------- 1 | 2 | ## Choropleth 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Choropleth 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/connected_scatterplot.md: -------------------------------------------------------------------------------- 1 | 2 | ## Connected Scatterplot 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Connected Scatterplot 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/correlation_matrix.md: -------------------------------------------------------------------------------- 1 | 2 | ## Correlation Matrix 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Correlation Matrix 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/cumulative.md: -------------------------------------------------------------------------------- 1 | 2 | ## Cumulative 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Cumulative 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/dashboards.md: -------------------------------------------------------------------------------- 1 | 2 | Just like the [plotting](../../../plotting/overview/#plotting) functions, generating dashboards with `dx` is **very** experimental and prone to change. 3 | ## [Making a Dashboard](../../reference/charts/basic_charts/#src.dx.plotting.dashboards.dashboard) 4 | If you create charts using `dx` functions, you may want to combine them into a single view or **dashboard**. This can be done with `dashboard()`. 5 | 6 | Similar to the chart functions, `dashboard()` mainly requires a pandas DataFrame, as well as a list of views in a matrix-like orientation. (Each item in the list is treated as a row, and each row can be a list of views to specify column positioning.) 7 | 8 | ### Simple 9 | Here's a quick example where we make a dashboard using two rows -- the top row will have [scatter](../../../plotting/basic_charts/#scatter) and [bar](../../../plotting/basic_charts/#bar) charts, and the bottom will be our default grid view: 10 | 11 | ```python 12 | dx.dashboard( 13 | df, 14 | views=[ 15 | ["scatter", "pie"], 16 | ["grid"], 17 | ], 18 | ) 19 | ``` 20 | ![](../screenshots/dashboard_simple1.png) 21 | 22 | ### With Chart Views 23 | Using chart functions, you can specify `return_view=True` and pass the resulting `DEXView` object into a dashboard. 24 | ```python 25 | custom_bar_chart = dx.bar( 26 | df, 27 | x='keyword_column', 28 | y='integer_column', 29 | column_sort_order='desc', 30 | column_sort_type='string', 31 | show_bar_labels=True, 32 | return_view=True, 33 | ) 34 | 35 | simple_tilemap = dx.tilemap( 36 | df, 37 | lat='index', 38 | lon='integer_column', 39 | return_view=True, 40 | ) 41 | 42 | dx.dashboard( 43 | df, 44 | views=[ 45 | ["parallel_coordinates", custom_bar_chart], 46 | [simple_tilemap, 'pie', 'grid'] 47 | ] 48 | ) 49 | ``` 50 | ![](../screenshots/dashboard_views1.png) 51 | 52 | ### Customized 53 | If you want to provide some additional arguments, instead of passing a string to indicate the chart type, you can pass a dictionary with `{"chart_mode": CHART TYPE, **extra_kwargs}`. 54 | ```python 55 | custom_chart = { 56 | "chart_mode": "hexbin", 57 | "decoration": { 58 | "title": "look at this sweet hexbin" 59 | }, 60 | } 61 | 62 | dx.dashboard( 63 | df, 64 | views=[ 65 | [custom_chart, "force_directed_network"], 66 | ["ridgeline"] 67 | ] 68 | ) 69 | ``` 70 | ![](../screenshots/dashboard_custom1.png) 71 | 72 | 73 | 74 | 75 | ## [Making a Dashboard](../../../plotting/overview/#making_a_dashboard) 76 | ::: src.dx.plotting.dashboards.dashboard 77 | -------------------------------------------------------------------------------- /docs/_charts/dataprism.md: -------------------------------------------------------------------------------- 1 | 2 | ## Chart Name 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Chart Name 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/dendrogram.md: -------------------------------------------------------------------------------- 1 | 2 | ## Dendrogram 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Dendrogram 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/dimension_matrix.md: -------------------------------------------------------------------------------- 1 | 2 | ## Dimension Matrix 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Dimension Matrix 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/diverging_bar.md: -------------------------------------------------------------------------------- 1 | 2 | ## Diverging Bar 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Diverging Bar 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/donut.md: -------------------------------------------------------------------------------- 1 | 2 | ## Donut 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Donut 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/dotplot.md: -------------------------------------------------------------------------------- 1 | 2 | ## Dot Plot 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Dot Plot 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/flow_diagram.md: -------------------------------------------------------------------------------- 1 | 2 | ## Flow Diagram 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Flow Diagram 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/force_directed_network.md: -------------------------------------------------------------------------------- 1 | 2 | ## Force-directed Network 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Force-directed Network 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/funnel.md: -------------------------------------------------------------------------------- 1 | 2 | ## Funnel 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Funnel 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/funnel_chart.md: -------------------------------------------------------------------------------- 1 | 2 | ## Funnel Chart 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Funnel Chart 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/funnel_sunburst.md: -------------------------------------------------------------------------------- 1 | 2 | ## Funnel Sunburst 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Funnel Sunburst 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/funnel_tree.md: -------------------------------------------------------------------------------- 1 | 2 | ## Funnel Tree 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Funnel Tree 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/heatmap.md: -------------------------------------------------------------------------------- 1 | 2 | ## Heatmap 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Heatmap 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/hexbin.md: -------------------------------------------------------------------------------- 1 | 2 | ## Hexbin 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Hexbin 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/histogram.md: -------------------------------------------------------------------------------- 1 | 2 | ## Histogram 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Histogram 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/horizon.md: -------------------------------------------------------------------------------- 1 | 2 | ## Horizon 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Horizon 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/line.md: -------------------------------------------------------------------------------- 1 | 2 | ## [Line](../../reference/charts/basic_charts/#src.dx.plotting.dex.basic_charts.line) 3 | 4 | ### Simple 5 | === "dx" 6 | 7 | ```python 8 | dx.line(df, x='datetime_column', y='integer_column') 9 | ``` 10 | ![](../screenshots/plotting_line_simple1.png) 11 | 12 | === "pd.options.plotting.backend = 'dx'" 13 | 14 | !!! info "Make sure you [enable `dx` as a pandas plotting backend](../plotting/overview.md#enabling-pandas-plotting-backend) first." 15 | 16 | ```python 17 | df.plot.line(x='datetime_column', y='integer_column') 18 | ``` 19 | ![](../screenshots/plotting_line_simple1_pd.png) 20 | 21 | ### Customized 22 | You may need to use a larger dataset to see the changes here. For these examples, we used `dx.random_dataframe(5000)`. 23 | 24 | === "dx" 25 | 26 | ```python 27 | dx.line( 28 | df, 29 | x='datetime_column', 30 | y='integer_column', 31 | line_type="cumulative", 32 | split_by="keyword_column", 33 | multi_axis=True, 34 | smoothing="hourly", 35 | use_count=True, 36 | bounding_type="relative", 37 | zero_baseline=True, 38 | combination_mode="min", 39 | ) 40 | ``` 41 | ![](../screenshots/plotting_line_custom1.png) 42 | 43 | === "pd.options.plotting.backend = 'dx'" 44 | 45 | !!! info "Make sure you [enable `dx` as a pandas plotting backend](../plotting/overview.md#enabling-pandas-plotting-backend) first." 46 | 47 | ```python 48 | df.plot.line( 49 | x='datetime_column', 50 | y='integer_column', 51 | line_type="cumulative", 52 | split_by="keyword_column", 53 | multi_axis=True, 54 | smoothing="hourly", 55 | use_count=True, 56 | bounding_type="relative", 57 | zero_baseline=True, 58 | combination_mode="min", 59 | ) 60 | ``` 61 | ![](../screenshots/plotting_line_custom1_pd.png) 62 | 63 | 64 | 65 | ## [Line](../../../plotting/basic_charts/#line) 66 | ::: src.dx.plotting.dex.basic_charts.line 67 | -------------------------------------------------------------------------------- /docs/_charts/line_percent.md: -------------------------------------------------------------------------------- 1 | 2 | ## Line Percent 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Line Percent 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/parallel_coordinates.md: -------------------------------------------------------------------------------- 1 | 2 | ## Parallel Coordinates 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Parallel Coordinates 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/partition.md: -------------------------------------------------------------------------------- 1 | 2 | ## Partition 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Partition 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/pie.md: -------------------------------------------------------------------------------- 1 | 2 | ## [Pie](../../reference/charts/basic_charts/#src.dx.plotting.dex.basic_charts.pie) 3 | 4 | ### Simple 5 | === "dx" 6 | 7 | ```python 8 | dx.pie(df, y='index') 9 | ``` 10 | ![](../screenshots/plotting_pie_simple1.png) 11 | 12 | === "pd.options.plotting.backend = 'dx'" 13 | 14 | !!! info "Make sure you [enable `dx` as a pandas plotting backend](../plotting/overview.md#enabling-pandas-plotting-backend) first." 15 | 16 | ```python 17 | df.plot.pie(y='index') 18 | ``` 19 | ![](../screenshots/plotting_pie_simple1_pd.png) 20 | 21 | ### Customized 22 | === "dx" 23 | 24 | ```python 25 | dx.pie( 26 | df, 27 | y='index', 28 | split_slices_by='keyword_column', 29 | show_total=False, 30 | pie_label_type='annotation', 31 | pie_label_contents='percent', 32 | ) 33 | ``` 34 | ![](../screenshots/plotting_pie_custom1.png) 35 | 36 | === "pd.options.plotting.backend = 'dx'" 37 | 38 | !!! info "Make sure you [enable `dx` as a pandas plotting backend](../plotting/overview.md#enabling-pandas-plotting-backend) first." 39 | 40 | ```python 41 | df.plot.pie( 42 | y='index', 43 | split_slices_by='keyword_column', 44 | show_total=False, 45 | pie_label_type='annotation', 46 | pie_label_contents='percent', 47 | ) 48 | ``` 49 | ![](../screenshots/plotting_pie_custom1_pd.png) 50 | 51 | 52 | 53 | ## [Pie](../../../plotting/basic_charts/#pie) 54 | ::: src.dx.plotting.dex.basic_charts.pie 55 | -------------------------------------------------------------------------------- /docs/_charts/radar_plot.md: -------------------------------------------------------------------------------- 1 | 2 | ## Radar Plot 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Radar Plot 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/ridgeline.md: -------------------------------------------------------------------------------- 1 | 2 | ## Ridgeline 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Ridgeline 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/sankey.md: -------------------------------------------------------------------------------- 1 | 2 | ## Sankey 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Sankey 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/scatter.md: -------------------------------------------------------------------------------- 1 | 2 | ## [Scatter](../../reference/charts/basic_charts/#src.dx.plotting.dex.basic_charts.scatterplot) 3 | 4 | ### Simple 5 | === "dx" 6 | 7 | ```python 8 | dx.scatter(df, x='float_column', y='integer_column') 9 | ``` 10 | ![](../screenshots/plotting_scatter_simple1.png) 11 | 12 | === "pd.options.plotting.backend = 'dx'" 13 | 14 | !!! info "Make sure you [enable `dx` as a pandas plotting backend](../plotting/overview.md#enabling-pandas-plotting-backend) first." 15 | 16 | !!! bug "Known Issue" 17 | `df.plot.scatter()` can be used unless `size` is specified. If you wish to use pandas syntax and provide a `size` argument, please use `df.plot(kind='scatter', ...)`. 18 | 19 | ```python 20 | df.plot(kind='scatter', x='float_column', y='integer_column') 21 | ``` 22 | ![](../screenshots/plotting_scatter_simple1_pd.png) 23 | 24 | ### Customized 25 | === "dx" 26 | 27 | ```python 28 | dx.scatter( 29 | df, 30 | x='float_column', 31 | y='integer_column', 32 | size='index', 33 | trend_line='polynomial', 34 | marginal_graphics='histogram', 35 | formula_display='r2' 36 | ) 37 | ``` 38 | ![](../screenshots/plotting_scatter_custom1.png) 39 | 40 | === "pd.options.plotting.backend = 'dx'" 41 | 42 | !!! info "Make sure you [enable `dx` as a pandas plotting backend](../plotting/overview.md#enabling-pandas-plotting-backend) first." 43 | 44 | !!! bug "Known Issue" 45 | `df.plot.scatter()` can be used unless `size` is specified. If you wish to use pandas syntax and provide a `size` argument, please use `df.plot(kind='scatter', ...)`. 46 | 47 | ```python 48 | df.plot( 49 | kind='scatter', 50 | x='float_column', 51 | y='integer_column', 52 | size='index', 53 | trend_line='polynomial', 54 | marginal_graphics='histogram', 55 | formula_display='r2' 56 | ) 57 | ``` 58 | ![](../screenshots/plotting_scatter_custom1_pd.png) 59 | 60 | 61 | 62 | ## [Scatter](../../../plotting/basic_charts/#scatter) 63 | ::: src.dx.plotting.dex.basic_charts.scatter 64 | -------------------------------------------------------------------------------- /docs/_charts/scatterplot_matrix.md: -------------------------------------------------------------------------------- 1 | 2 | ## Scatterplot Matrix 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Scatterplot Matrix 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/stacked_area.md: -------------------------------------------------------------------------------- 1 | 2 | ## Stacked Area 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Stacked Area 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/stacked_percent.md: -------------------------------------------------------------------------------- 1 | 2 | ## Stacked Percent 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Stacked Percent 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/sunburst.md: -------------------------------------------------------------------------------- 1 | 2 | ## Sunburst 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Sunburst 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/tilemap.md: -------------------------------------------------------------------------------- 1 | 2 | ## [Tilemap](../../reference/charts/maps/#src.dx.plotting.dex.map_charts.tilemap) 3 | Since `dx.random_dataframe()` returns `integer_column` values (`-100` to `100`) and `float_column` values (`0.0` to `1.0`) as the only numeric columns by default, we can suggest enabling the `lat_float_column` and `lon_float_column` arguments for some quick testing: 4 | ```python 5 | df = dx.random_dataframe(100, lat_float_column=True, lon_float_column=True) 6 | ``` 7 | 8 | !!! info "" 9 | More about how Noteable builds with Mapbox [here](https://www.mapbox.com/showcase/noteable). 🗺️ 10 | ### Simple 11 | === "dx" 12 | 13 | ```python 14 | dx.tilemap(df, lon='lon_float_column', lat='lat_float_column') 15 | ``` 16 | ![](../screenshots/plotting_tilemap_simple1.png) 17 | 18 | === "pd.options.plotting.backend = 'dx'" 19 | 20 | !!! info "Make sure you [enable `dx` as a pandas plotting backend](../plotting/overview.md#enabling-pandas-plotting-backend) first." 21 | 22 | ```python 23 | df.plot(kind='tilemap', lon='lon_float_column', lat='lat_float_column') 24 | ``` 25 | _*Note you can't use `df.plot.tilemap()` directly_ 26 | 27 | ![](../screenshots/plotting_tilemap_simple1_pd.png) 28 | 29 | ### Customized 30 | === "dx" 31 | 32 | ```python 33 | dx.tilemap( 34 | df, 35 | lat='lat_float_column', 36 | lon='lon_float_column', 37 | icon_opacity=0.5, 38 | icon_size='index', 39 | icon_size_scale="log", 40 | stroke_color="magenta", 41 | stroke_width=5, 42 | label_column='bytes_column', 43 | tile_layer="light", 44 | hover_cols=['keyword_column', 'datetime_column'], 45 | ) 46 | ``` 47 | ![](../screenshots/plotting_tilemap_custom1.png) 48 | 49 | === "pd.options.plotting.backend = 'dx'" 50 | 51 | !!! info "Make sure you [enable `dx` as a pandas plotting backend](../plotting/overview.md#enabling-pandas-plotting-backend) first." 52 | 53 | ```python 54 | df.plot( 55 | kind='tilemap', 56 | lat='lat_float_column', 57 | lon='lon_float_column', 58 | icon_opacity=0.5, 59 | icon_size='index', 60 | icon_size_scale="log", 61 | stroke_color="magenta", 62 | stroke_width=5, 63 | label_column='bytes_column', 64 | tile_layer="light", 65 | hover_cols=['keyword_column', 'datetime_column'], 66 | ) 67 | 68 | ``` 69 | _*Note you can't use `df.plot.tilemap()` directly_ 70 | 71 | ![](../screenshots/plotting_tilemap_custom1_pd.png) 72 | 73 | 74 | 75 | ## [Tilemap](../../../plotting/maps/#tilemap) 76 | ::: src.dx.plotting.dex.map_charts.tilemap 77 | -------------------------------------------------------------------------------- /docs/_charts/treemap.md: -------------------------------------------------------------------------------- 1 | 2 | ## Treemap 3 | Coming soon! 4 | 44 | 45 | 46 | 47 | 48 | ## Treemap 49 | Coming soon! 50 | 51 | -------------------------------------------------------------------------------- /docs/_charts/violin.md: -------------------------------------------------------------------------------- 1 | 2 | ## [Violin](../../reference/charts/basic_charts/#src.dx.plotting.dex.summary_charts.violin) 3 | 4 | ### Simple 5 | === "dx" 6 | 7 | ```python 8 | dx.violin(df, split_by='keyword_column', metric='integer_column') 9 | ``` 10 | ![](../screenshots/plotting_violin_simple1.png) 11 | 12 | === "pd.options.plotting.backend = 'dx'" 13 | 14 | !!! info "Make sure you [enable `dx` as a pandas plotting backend](../plotting/overview.md#enabling-pandas-plotting-backend) first." 15 | 16 | ```python 17 | df.plot(kind='violin', split_by='keyword_column', metric='integer_column') 18 | ``` 19 | _*Note you can't use `df.plot.violin()` directly_ 20 | 21 | ![](../screenshots/plotting_violin_simple1_pd.png) 22 | 23 | ### Customized 24 | === "dx" 25 | 26 | ```python 27 | dx.violin( 28 | df, 29 | split_by='keyword_column', 30 | metric='integer_column', 31 | bins=5, 32 | show_interquartile_range=True, 33 | column_sort_order='desc', 34 | ) 35 | ``` 36 | ![](../screenshots/plotting_violin_custom1.png) 37 | 38 | === "pd.options.plotting.backend = 'dx'" 39 | 40 | !!! info "Make sure you [enable `dx` as a pandas plotting backend](../plotting/overview.md#enabling-pandas-plotting-backend) first." 41 | 42 | ```python 43 | df.plot( 44 | kind='violin', 45 | split_by='keyword_column', 46 | metric='integer_column', 47 | bins=5, 48 | show_interquartile_range=True, 49 | column_sort_order='desc', 50 | ) 51 | ``` 52 | _*Note you can't use `df.plot.violin()` directly_ 53 | 54 | ![](../screenshots/plotting_violin_custom1_pd.png) 55 | 56 | 57 | 58 | ## [Violin](../../../plotting/summary_charts/#violin) 59 | ::: src.dx.plotting.dex.summary_charts.violin 60 | -------------------------------------------------------------------------------- /docs/_charts/wordcloud.md: -------------------------------------------------------------------------------- 1 | 2 | ## [Wordcloud](../../reference/charts/basic_charts/#src.dx.plotting.dex.basic_charts.wordcloud) 3 | ### Simple 4 | === "dx" 5 | 6 | ```python 7 | dx.wordcloud(df, word_column='keyword_column', size='float_column') 8 | ``` 9 | ![](../screenshots/plotting_wordcloud_simple1.png) 10 | 11 | === "pd.options.plotting.backend = 'dx'" 12 | 13 | !!! info "Make sure you [enable `dx` as a pandas plotting backend](../plotting/overview.md#enabling-pandas-plotting-backend) first." 14 | 15 | ```python 16 | df.plot(kind='wordcloud', word_column='keyword_column', size='float_column') 17 | ``` 18 | _*Note you can't use `df.plot.wordcloud()` directly_ 19 | 20 | ![](../screenshots/plotting_wordcloud_simple1_pd.png) 21 | 22 | ### Customized 23 | === "dx" 24 | 25 | ```python 26 | dx.wordcloud( 27 | df, 28 | word_column='dtype_column', 29 | size='float_column', 30 | text_format='token', 31 | word_rotation='45', 32 | random_coloring=True, 33 | ) 34 | ``` 35 | ![](../screenshots/plotting_wordcloud_custom1.png) 36 | 37 | === "pd.options.plotting.backend = 'dx'" 38 | 39 | !!! info "Make sure you [enable `dx` as a pandas plotting backend](../plotting/overview.md#enabling-pandas-plotting-backend) first." 40 | 41 | ```python 42 | df.plot( 43 | kind='wordcloud', 44 | word_column='keyword_column', 45 | word_column='dtype_column', 46 | size='float_column', 47 | text_format='token', 48 | word_rotation='45', 49 | random_coloring=True, 50 | ) 51 | ``` 52 | _*Note you can't use `df.plot.wordcloud()` directly_ 53 | 54 | ![](../screenshots/plotting_wordcloud_custom1_pd.png) 55 | 56 | 57 | 58 | ## [Wordcloud](../../../plotting/basic_charts/#wordcloud) 59 | ::: src.dx.plotting.dex.basic_charts.wordcloud 60 | -------------------------------------------------------------------------------- /docs/plotting/basic_charts.md: -------------------------------------------------------------------------------- 1 | # Basic Charts 2 | 3 | Here we'll talk about how to plot some basic chart types in DEX using `dx`. 4 | 5 | 6 | ## Setup 7 | We will be using our own built-in DataFrame generation function for these visualizations. 8 | The values you see may be different if you run the same code in a cell, but the column structure should be very similar (if not identical). 9 | ```python 10 | df = dx.random_dataframe(100) 11 | ``` 12 | 13 | !!! warning "The _**Customized**_ examples with more options do not necessarily represent "good" data visualization; they are just a glimpse into what settings are available to compare against the _**Simple**_ examples." 14 | 15 | 16 | --8<-- "./docs/_charts/bar.md:usage" 17 | --8<-- "./docs/_charts/dataprism.md:usage" 18 | --8<-- "./docs/_charts/line.md:usage" 19 | --8<-- "./docs/_charts/pie.md:usage" 20 | --8<-- "./docs/_charts/scatter.md:usage" 21 | --8<-- "./docs/_charts/tilemap.md:usage" 22 | --8<-- "./docs/_charts/violin.md:usage" 23 | --8<-- "./docs/_charts/wordcloud.md:usage" 24 | -------------------------------------------------------------------------------- /docs/plotting/comparison_charts.md: -------------------------------------------------------------------------------- 1 | # Comparison Charts 2 | 3 | Here we'll talk about how to plot some comparison chart types in DEX using `dx`. 4 | 5 | --8<-- "./docs/plotting/basic_charts.md:setup" 6 | 7 | --8<-- "./docs/_charts/bar.md:usage" 8 | --8<-- "./docs/_charts/connected_scatterplot.md:usage" 9 | --8<-- "./docs/_charts/correlation_matrix.md:usage" 10 | --8<-- "./docs/_charts/diverging_bar.md:usage" 11 | --8<-- "./docs/_charts/dotplot.md:usage" 12 | --8<-- "./docs/_charts/radar_plot.md:usage" 13 | --8<-- "./docs/_charts/parallel_coordinates.md:usage" 14 | --8<-- "./docs/_charts/scatter.md:usage" 15 | --8<-- "./docs/_charts/scatterplot_matrix.md:usage" 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/plotting/conditional_formatting.md: -------------------------------------------------------------------------------- 1 | # Conditional formatting 2 | 3 | Coming soon! 4 | -------------------------------------------------------------------------------- /docs/plotting/dashboards.md: -------------------------------------------------------------------------------- 1 | --8<-- "./docs/_charts/dashboards.md:usage" -------------------------------------------------------------------------------- /docs/plotting/faceting.md: -------------------------------------------------------------------------------- 1 | # Faceting 2 | 3 | Coming soon! 4 | -------------------------------------------------------------------------------- /docs/plotting/funnel_charts.md: -------------------------------------------------------------------------------- 1 | # Funnel Charts 2 | 3 | Here we'll talk about how to plot some funnel chart types in DEX using `dx`. 4 | 5 | --8<-- "./docs/plotting/basic_charts.md:setup" 6 | 7 | --8<-- "./docs/_charts/flow_diagram.md:usage" 8 | --8<-- "./docs/_charts/funnel.md:usage" 9 | --8<-- "./docs/_charts/funnel_chart.md:usage" 10 | --8<-- "./docs/_charts/funnel_sunburst.md:usage" 11 | --8<-- "./docs/_charts/funnel_tree.md:usage" 12 | -------------------------------------------------------------------------------- /docs/plotting/maps.md: -------------------------------------------------------------------------------- 1 | # Maps 2 | 3 | Here we'll talk about how to plot some maps in DEX using `dx`. 4 | 5 | --8<-- "./docs/plotting/basic_charts.md:setup" 6 | 7 | --8<-- "./docs/_charts/choropleth.md:usage" 8 | --8<-- "./docs/_charts/tilemap.md:usage" -------------------------------------------------------------------------------- /docs/plotting/part_to_whole_charts.md: -------------------------------------------------------------------------------- 1 | # Part-to-whole Charts 2 | 3 | Here we'll talk about how to plot some part-to-whole chart types in DEX using `dx`. 4 | 5 | --8<-- "./docs/plotting/basic_charts.md:setup" 6 | 7 | --8<-- "./docs/_charts/donut.md:usage" 8 | --8<-- "./docs/_charts/partition.md:usage" 9 | --8<-- "./docs/_charts/pie.md:usage" 10 | --8<-- "./docs/_charts/sunburst.md:usage" 11 | --8<-- "./docs/_charts/treemap.md:usage" -------------------------------------------------------------------------------- /docs/plotting/relationship_charts.md: -------------------------------------------------------------------------------- 1 | # Relationship Charts 2 | 3 | Here we'll talk about how to plot some relationship chart types in DEX using `dx`. 4 | 5 | --8<-- "./docs/plotting/basic_charts.md:setup" 6 | 7 | --8<-- "./docs/_charts/arc_diagram.md:usage" 8 | --8<-- "./docs/_charts/adjacency_matrix.md:usage" 9 | --8<-- "./docs/_charts/dendrogram.md:usage" 10 | --8<-- "./docs/_charts/force_directed_network.md:usage" 11 | --8<-- "./docs/_charts/sankey.md:usage" 12 | -------------------------------------------------------------------------------- /docs/plotting/summary_charts.md: -------------------------------------------------------------------------------- 1 | # Summary Charts 2 | 3 | Here we'll talk about how to plot some summary chart types in DEX using `dx`. 4 | 5 | --8<-- "./docs/plotting/basic_charts.md:setup" 6 | 7 | --8<-- "./docs/_charts/big_number.md:usage" 8 | --8<-- "./docs/_charts/boxplot.md:usage" 9 | --8<-- "./docs/_charts/dimension_matrix.md:usage" 10 | --8<-- "./docs/_charts/heatmap.md:usage" 11 | --8<-- "./docs/_charts/hexbin.md:usage" 12 | --8<-- "./docs/_charts/histogram.md:usage" 13 | --8<-- "./docs/_charts/horizon.md:usage" 14 | --8<-- "./docs/_charts/ridgeline.md:usage" 15 | --8<-- "./docs/_charts/violin.md:usage" -------------------------------------------------------------------------------- /docs/plotting/time_series_charts.md: -------------------------------------------------------------------------------- 1 | # Time Series Charts 2 | 3 | Here we'll talk about how to plot some time series chart types in DEX using `dx`. 4 | 5 | --8<-- "./docs/plotting/basic_charts.md:setup" 6 | 7 | --8<-- "./docs/_charts/candlestick.md:usage" 8 | --8<-- "./docs/_charts/cumulative.md:usage" 9 | --8<-- "./docs/_charts/line.md:usage" 10 | --8<-- "./docs/_charts/line_percent.md:usage" 11 | --8<-- "./docs/_charts/stacked_area.md:usage" 12 | --8<-- "./docs/_charts/stacked_percent.md:usage" 13 | -------------------------------------------------------------------------------- /docs/reference/charts/basic_charts.md: -------------------------------------------------------------------------------- 1 | # Basic Charts 2 | 3 | --8<-- "./docs/_charts/bar.md:ref" 4 | 5 | --8<-- "./docs/_charts/line.md:ref" 6 | 7 | --8<-- "./docs/_charts/pie.md:ref" 8 | 9 | --8<-- "./docs/_charts/scatter.md:ref" 10 | 11 | --8<-- "./docs/_charts/tilemap.md:ref" 12 | 13 | --8<-- "./docs/_charts/violin.md:ref" 14 | 15 | --8<-- "./docs/_charts/wordcloud.md:ref" 16 | 17 | --8<-- "./docs/_charts/dataprism.md:ref" -------------------------------------------------------------------------------- /docs/reference/charts/comparison_charts.md: -------------------------------------------------------------------------------- 1 | # Comparison Charts 2 | 3 | --8<-- "./docs/_charts/parallel_coordinates.md:ref" 4 | 5 | --8<-- "./docs/_charts/scatter.md:ref" 6 | 7 | --8<-- "./docs/_charts/connected_scatterplot.md:ref" 8 | 9 | --8<-- "./docs/_charts/scatterplot_matrix.md:ref" 10 | 11 | --8<-- "./docs/_charts/correlation_matrix.md:ref" 12 | 13 | --8<-- "./docs/_charts/bar.md:ref" 14 | 15 | --8<-- "./docs/_charts/dotplot.md:ref" 16 | 17 | --8<-- "./docs/_charts/radar_plot.md:ref" 18 | 19 | --8<-- "./docs/_charts/diverging_bar.md:ref" 20 | 21 | -------------------------------------------------------------------------------- /docs/reference/charts/dashboards.md: -------------------------------------------------------------------------------- 1 | --8<-- "./docs/_charts/dashboards.md:ref" -------------------------------------------------------------------------------- /docs/reference/charts/funnel_charts.md: -------------------------------------------------------------------------------- 1 | # Funnel Charts 2 | 3 | --8<-- "./docs/_charts/funnel.md:ref" 4 | 5 | --8<-- "./docs/_charts/funnel_chart.md:ref" 6 | 7 | --8<-- "./docs/_charts/funnel_tree.md:ref" 8 | 9 | --8<-- "./docs/_charts/funnel_sunburst.md:ref" 10 | 11 | --8<-- "./docs/_charts/flow_diagram.md:ref" 12 | 13 | --8<-- "./docs/_charts/arc_flow.md:ref" -------------------------------------------------------------------------------- /docs/reference/charts/maps.md: -------------------------------------------------------------------------------- 1 | # Maps 2 | 3 | --8<-- "./docs/_charts/tilemap.md:ref" 4 | 5 | --8<-- "./docs/_charts/choropleth.md:ref" -------------------------------------------------------------------------------- /docs/reference/charts/part_to_whole_charts.md: -------------------------------------------------------------------------------- 1 | # Part-to-Whole Charts 2 | 3 | --8<-- "./docs/_charts/pie.md:ref" 4 | 5 | --8<-- "./docs/_charts/donut.md:ref" 6 | 7 | --8<-- "./docs/_charts/sunburst.md:ref" 8 | 9 | --8<-- "./docs/_charts/treemap.md:ref" 10 | 11 | --8<-- "./docs/_charts/partition.md:ref" -------------------------------------------------------------------------------- /docs/reference/charts/relationship_charts.md: -------------------------------------------------------------------------------- 1 | # Relationship Charts 2 | 3 | --8<-- "./docs/_charts/force_directed_network.md:ref" 4 | 5 | --8<-- "./docs/_charts/sankey.md:ref" 6 | 7 | --8<-- "./docs/_charts/arc_diagram.md:ref" 8 | 9 | --8<-- "./docs/_charts/adjacency_matrix.md:ref" 10 | 11 | --8<-- "./docs/_charts/dendrogram.md:ref" -------------------------------------------------------------------------------- /docs/reference/charts/summary_charts.md: -------------------------------------------------------------------------------- 1 | # Funnel Charts 2 | 3 | --8<-- "./docs/_charts/big_number.md:ref" 4 | 5 | --8<-- "./docs/_charts/wordcloud.md:ref" 6 | 7 | --8<-- "./docs/_charts/dimension_matrix.md:ref" 8 | 9 | --8<-- "./docs/_charts/violin.md:ref" 10 | 11 | --8<-- "./docs/_charts/boxplot.md:ref" 12 | 13 | --8<-- "./docs/_charts/heatmap.md:ref" 14 | 15 | --8<-- "./docs/_charts/histogram.md:ref" 16 | 17 | --8<-- "./docs/_charts/ridgeline.md:ref" 18 | 19 | --8<-- "./docs/_charts/horizon.md:ref" 20 | 21 | --8<-- "./docs/_charts/hexbin.md:ref" -------------------------------------------------------------------------------- /docs/reference/charts/time_series_charts.md: -------------------------------------------------------------------------------- 1 | # Time Series Charts 2 | 3 | --8<-- "./docs/_charts/line.md:ref" 4 | 5 | --8<-- "./docs/_charts/cumulative.md:ref" 6 | 7 | --8<-- "./docs/_charts/stacked_area.md:ref" 8 | 9 | --8<-- "./docs/_charts/line_percent.md:ref" 10 | 11 | --8<-- "./docs/_charts/stacked_percent.md:ref" 12 | 13 | --8<-- "./docs/_charts/candlestick.md:ref" -------------------------------------------------------------------------------- /docs/reference/datatypes/compatibility.md: -------------------------------------------------------------------------------- 1 | ::: src.dx.datatypes.compatibility.test_compatibility 2 | ::: src.dx.datatypes.compatibility.test_build_table_schema 3 | ::: src.dx.datatypes.compatibility.test_json_clean 4 | ::: src.dx.datatypes.compatibility.test_db_write 5 | ::: src.dx.datatypes.compatibility.test_dx_handling -------------------------------------------------------------------------------- /docs/reference/datatypes/dataframes.md: -------------------------------------------------------------------------------- 1 | ::: src.dx.datatypes.main.quick_random_dataframe 2 | ::: src.dx.datatypes.main.random_dataframe -------------------------------------------------------------------------------- /docs/reference/datatypes/datetime.md: -------------------------------------------------------------------------------- 1 | ::: src.dx.datatypes.date_time.generate_datetime_series 2 | ::: src.dx.datatypes.date_time.generate_date_series 3 | ::: src.dx.datatypes.date_time.generate_time_series 4 | ::: src.dx.datatypes.date_time.generate_time_period_series 5 | ::: src.dx.datatypes.date_time.generate_time_interval_series 6 | ::: src.dx.datatypes.date_time.generate_time_delta_series -------------------------------------------------------------------------------- /docs/reference/datatypes/geometry.md: -------------------------------------------------------------------------------- 1 | ::: src.dx.datatypes.geometry.generate_lat_float_series 2 | ::: src.dx.datatypes.geometry.generate_lon_float_series 3 | ::: src.dx.datatypes.geometry.generate_latlon_series 4 | ::: src.dx.datatypes.geometry.generate_filled_geojson_series 5 | ::: src.dx.datatypes.geometry.generate_exterior_bounds_geojson_series -------------------------------------------------------------------------------- /docs/reference/datatypes/misc.md: -------------------------------------------------------------------------------- 1 | ::: src.dx.datatypes.misc.generate_boolean_series 2 | ::: src.dx.datatypes.misc.generate_bytes_series 3 | ::: src.dx.datatypes.misc.generate_dict_series 4 | ::: src.dx.datatypes.misc.generate_dtype_series 5 | ::: src.dx.datatypes.misc.generate_list_series 6 | ::: src.dx.datatypes.misc.generate_ipv4_series 7 | ::: src.dx.datatypes.misc.generate_ipv6_series 8 | ::: src.dx.datatypes.misc.generate_uuid4_series -------------------------------------------------------------------------------- /docs/reference/datatypes/numeric.md: -------------------------------------------------------------------------------- 1 | ::: src.dx.datatypes.numeric.generate_integer_series 2 | ::: src.dx.datatypes.numeric.generate_float_series 3 | ::: src.dx.datatypes.numeric.generate_decimal_series 4 | ::: src.dx.datatypes.numeric.generate_complex_number_series -------------------------------------------------------------------------------- /docs/reference/datatypes/overview.md: -------------------------------------------------------------------------------- 1 | While testing rendering and formatting compatibility across different frameworks, you may need to have quick access to different data types of varying sizes. Below are some convenience functions to help. 2 | 3 | ## `DataFrame` objects 4 | - [DataFrames](dataframes.md) 5 | 6 | ## `Series` objects 7 | - [Numeric](numeric.md) 8 | - [Text](text.md) 9 | - [Datetime](datetime.md) 10 | - [Geometry](geometry.md) 11 | - [Misc](misc.md) 12 | - [Checking Compatibility](compatibility.md) -------------------------------------------------------------------------------- /docs/reference/datatypes/text.md: -------------------------------------------------------------------------------- 1 | ::: src.dx.datatypes.text.generate_text_series 2 | ::: src.dx.datatypes.text.generate_keyword_series -------------------------------------------------------------------------------- /docs/screenshots/attrs_view1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/attrs_view1.png -------------------------------------------------------------------------------- /docs/screenshots/dashboard_custom1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/dashboard_custom1.png -------------------------------------------------------------------------------- /docs/screenshots/dashboard_simple1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/dashboard_simple1.png -------------------------------------------------------------------------------- /docs/screenshots/dashboard_views1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/dashboard_views1.png -------------------------------------------------------------------------------- /docs/screenshots/dx_display_sample1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/dx_display_sample1.png -------------------------------------------------------------------------------- /docs/screenshots/dx_display_sample2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/dx_display_sample2.png -------------------------------------------------------------------------------- /docs/screenshots/dx_display_sample3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/dx_display_sample3.png -------------------------------------------------------------------------------- /docs/screenshots/dx_plain_mode_sample1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/dx_plain_mode_sample1.png -------------------------------------------------------------------------------- /docs/screenshots/dx_settings_sample1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/dx_settings_sample1.png -------------------------------------------------------------------------------- /docs/screenshots/dx_settings_sample2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/dx_settings_sample2.png -------------------------------------------------------------------------------- /docs/screenshots/dx_settings_sample3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/dx_settings_sample3.png -------------------------------------------------------------------------------- /docs/screenshots/dx_simple_mode_sample1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/dx_simple_mode_sample1.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_bar_custom1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_bar_custom1.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_bar_custom1_pd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_bar_custom1_pd.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_bar_simple1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_bar_simple1.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_bar_simple1_pd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_bar_simple1_pd.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_line_custom1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_line_custom1.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_line_custom1_pd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_line_custom1_pd.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_line_simple1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_line_simple1.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_line_simple1_pd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_line_simple1_pd.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_pie_custom1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_pie_custom1.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_pie_custom1_pd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_pie_custom1_pd.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_pie_simple1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_pie_simple1.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_pie_simple1_pd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_pie_simple1_pd.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_scatter_custom1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_scatter_custom1.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_scatter_custom1_pd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_scatter_custom1_pd.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_scatter_simple1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_scatter_simple1.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_scatter_simple1_pd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_scatter_simple1_pd.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_tilemap_custom1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_tilemap_custom1.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_tilemap_custom1_pd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_tilemap_custom1_pd.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_tilemap_simple1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_tilemap_simple1.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_tilemap_simple1_pd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_tilemap_simple1_pd.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_violin_custom1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_violin_custom1.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_violin_custom1_pd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_violin_custom1_pd.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_violin_simple1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_violin_simple1.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_violin_simple1_pd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_violin_simple1_pd.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_wordcloud_custom1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_wordcloud_custom1.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_wordcloud_custom1_pd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_wordcloud_custom1_pd.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_wordcloud_simple1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_wordcloud_simple1.png -------------------------------------------------------------------------------- /docs/screenshots/plotting_wordcloud_simple1_pd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/docs/screenshots/plotting_wordcloud_simple1_pd.png -------------------------------------------------------------------------------- /noxfile.py: -------------------------------------------------------------------------------- 1 | import nox 2 | import nox_poetry 3 | 4 | LINT_PATHS = ["src/dx", "noxfile.py", "tests"] 5 | 6 | nox.options.reuse_existing_virtualenv = True 7 | nox.options.sessions = ["lint", "test"] 8 | 9 | 10 | @nox_poetry.session(python=["3.8", "3.9", "3.10"]) 11 | def test(session: nox_poetry.Session): 12 | session.run_always("poetry", "install", external=True) 13 | session.run("pytest", "-v", "--cov=src/dx") 14 | 15 | 16 | @nox_poetry.session(python="3.8") 17 | def lint(session: nox_poetry.Session): 18 | session.notify("black_check") 19 | session.notify("flake8") 20 | session.notify("isort_check") 21 | 22 | 23 | @nox_poetry.session(python="3.8") 24 | def flake8(session: nox_poetry.Session): 25 | session.install("flake8") 26 | session.run("flake8", *LINT_PATHS, "--count", "--show-source", "--statistics", "--benchmark") 27 | 28 | 29 | @nox_poetry.session(python="3.8") 30 | def black_check(session: nox_poetry.Session): 31 | session.install("black") 32 | session.run("black", "--check", *LINT_PATHS) 33 | 34 | 35 | @nox_poetry.session(python="3.8") 36 | def isort_check(session: nox_poetry.Session): 37 | session.install("isort") 38 | session.run("isort", "--diff", "--check", *LINT_PATHS) 39 | 40 | 41 | @nox_poetry.session(python="3.8") 42 | def blacken(session: nox_poetry.Session): 43 | session.install("black") 44 | session.run("black", *LINT_PATHS) 45 | 46 | 47 | @nox_poetry.session(python="3.8") 48 | def isort_apply(session: nox_poetry.Session): 49 | session.install("isort") 50 | session.run("isort", *LINT_PATHS) 51 | 52 | 53 | @nox_poetry.session(python="3.8") 54 | def generate_coverage_xml(session: nox_poetry.Session): 55 | session.install("coverage[toml]") 56 | session.run("coverage", "xml") 57 | 58 | 59 | @nox_poetry.session(python="3.8") 60 | def publish_docs(session: nox_poetry.Session): 61 | session.run_always("poetry", "install", "-E", "docs", external=True) 62 | session.run("mkdocs", "gh-deploy", "--force") 63 | -------------------------------------------------------------------------------- /poetry.toml: -------------------------------------------------------------------------------- 1 | [virtualenvs] 2 | create = true 3 | in-project = true 4 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "dx" 3 | version = "1.4.0" 4 | description = "Python wrapper for Data Explorer" 5 | authors = [ 6 | "Dave Shoup ", 7 | "Kyle Kelley ", 8 | ] 9 | readme = "README.md" 10 | license = "MIT" 11 | homepage = "https://app.noteable.io/" 12 | repository = "https://github.com/noteable-io/dx" 13 | keywords = ["data", "exploration", "visualization"] 14 | 15 | [tool.poetry.dependencies] 16 | python = "^3.9" 17 | pandas = "^1.3.5" 18 | ipython = ">=7.31.1" 19 | pydantic = "^1.9" 20 | mkdocs = { version = "^1.3.1", optional = true } 21 | mkdocs-material = { version = "^8.3.9", optional = true } 22 | mkdocs-jupyter = { version = ">=0.21,<0.23", optional = true } 23 | mkdocstrings = { version = ">=0.19,<0.22", optional = true } 24 | mkdocstrings-python = { version = ">=0.7.1,<0.10.0", optional = true } 25 | duckdb-engine = "^0.9.2" 26 | exceptiongroup = "^1.0.4" 27 | repr-llm = "^0.3.0" 28 | structlog = "^23.2.0" 29 | 30 | [tool.poetry.group.dev.dependencies] 31 | black = ">=22.12,<24.0" 32 | flake8 = "^5.0.4" 33 | flake8-docstrings = "^1.6.0" 34 | isort = "^5.11.4" 35 | nox = "^2022.11.21" 36 | nox-poetry = "^1.0.2" 37 | pytest = "^7.2.0" 38 | pytest-benchmark = "^4.0.0" 39 | pytest-cov = "^4.0.0" 40 | pytest-mock = "^3.10.0" 41 | # for datatype testing 42 | Faker = ">=15.3.4,<19.0.0" 43 | geopandas = "^0.12.2" 44 | polars = ">=0.16.12,<0.18.0" 45 | modin = { extras = ["all"], version = "^0.19.0" } 46 | dask = "^2023.3.1" 47 | vaex = "^4.16.0" 48 | 49 | [tool.poetry.extras] 50 | docs = [ 51 | "mkdocs", 52 | "mkdocs-material", 53 | "mkdocs-jupyter", 54 | "mkdocstrings", 55 | "mkdocstrings-python", 56 | "mkdocs-glightbox", 57 | ] 58 | 59 | [build-system] 60 | requires = ["poetry_core>=1.0.0"] 61 | build-backend = "poetry.core.masonry.api" 62 | 63 | [tool.isort] 64 | profile = "black" 65 | line_length = 100 66 | 67 | [tool.black] 68 | line-length = 100 69 | 70 | [tool.coverage.run] 71 | branch = false 72 | omit = ["*/tests/*"] 73 | 74 | [tool.coverage.report] 75 | exclude_lines = [ 76 | "if self.debug:", 77 | "pragma: no cover", 78 | "raise AssertionError", 79 | "raise NotImplementedError", 80 | "if __name__ == '__main__':", 81 | ] 82 | ignore_errors = true 83 | omit = [] 84 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = tests 3 | 4 | markers = 5 | benchmark: Mark a test as a benchmark test for performance purposes. 6 | -------------------------------------------------------------------------------- /requirements-docs.txt: -------------------------------------------------------------------------------- 1 | mkdocs 2 | mkdocs-material 3 | mkdocs-jupyter 4 | mkdocstrings 5 | mkdocstrings-python 6 | mkdocs-glightbox -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # Flake8 does not support reading pyproject.toml 2 | # so this stays here for now. 3 | # We'd like to use flakehell but it's not working on latest version of flake8. 4 | 5 | [flake8] 6 | # References: 7 | # https://flake8.readthedocs.io/en/latest/user/configuration.html 8 | # https://flake8.readthedocs.io/en/latest/user/error-codes.html 9 | 10 | # Note: there cannot be spaces after comma's here 11 | exclude = __init__.py 12 | select = 13 | C, 14 | E, 15 | F, 16 | W, 17 | B, 18 | B950, 19 | # docstrings must be triple-quoted, via flake8-docstrings 20 | D300 21 | ignore = 22 | # "Too complex" 23 | C901, 24 | # Extra space in brackets 25 | E20, 26 | E203, 27 | # Multiple spaces around "," 28 | E231,E241, 29 | # Comments 30 | E26, 31 | # Import formatting 32 | E4, 33 | E501, 34 | # Comparing types instead of isinstance 35 | E721, 36 | # Assigning lambda expression 37 | E731, 38 | W503 39 | max-line-length = 100 40 | max-complexity = 23 41 | -------------------------------------------------------------------------------- /src/dx/__init__.py: -------------------------------------------------------------------------------- 1 | from dx.comms import handle_assignment_comm, handle_resample_comm 2 | from dx.datatypes import * 3 | from dx.dx import display, show_docs 4 | from dx.formatters import * 5 | from dx.loggers import configure_logging 6 | from dx.plotting import * 7 | from dx.settings import * 8 | 9 | __version__ = "1.4.0" 10 | 11 | configure_logging() 12 | -------------------------------------------------------------------------------- /src/dx/comms/__init__.py: -------------------------------------------------------------------------------- 1 | from dx.comms.assignment import handle_assignment_comm 2 | from dx.comms.resample import handle_resample_comm 3 | -------------------------------------------------------------------------------- /src/dx/comms/assignment.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import structlog 4 | from IPython import get_ipython 5 | from IPython.core.interactiveshell import InteractiveShell 6 | 7 | from dx.filtering import resample_from_db 8 | from dx.types.filters import DEXFilterSettings 9 | from dx.utils.formatting import incrementing_label 10 | 11 | logger = structlog.get_logger(__name__) 12 | 13 | 14 | # ref: https://jupyter-notebook.readthedocs.io/en/stable/comms.html#opening-a-comm-from-the-frontend 15 | def dataframe_assignment(comm, open_msg): 16 | """ 17 | Datalink resample request. 18 | """ 19 | 20 | @comm.on_msg 21 | def _recv(msg): 22 | # Is separate function to make testing easier. 23 | handle_assignment_comm(msg) 24 | 25 | comm.send({"status": "connected", "source": "dataframe_assignment"}) 26 | 27 | 28 | def handle_assignment_comm(msg: dict, ipython_shell: Optional[InteractiveShell] = None): 29 | data = msg.get("content", {}).get("data", {}) 30 | if not data: 31 | return 32 | 33 | if "display_id" in data and "variable_name" in data: 34 | filters = data["filters"] 35 | sample_size = data["sample_size"] 36 | 37 | sql_filter = f"SELECT * FROM {{table_name}} LIMIT {sample_size}" 38 | if filters: 39 | dex_filters = DEXFilterSettings(filters=filters) 40 | sql_filter_str = dex_filters.to_sql_query() 41 | sql_filter = f"SELECT * FROM {{table_name}} WHERE {sql_filter_str} LIMIT {sample_size}" 42 | 43 | sampled_df = resample_from_db( 44 | display_id=data["display_id"], 45 | sql_filter=sql_filter, 46 | filters=filters, 47 | assign_subset=False, 48 | ) 49 | 50 | ipython = ipython_shell or get_ipython() 51 | variable_name = data["variable_name"] 52 | 53 | # if the variable already exists in the user namespace, add a suffix so the previous value isn't overwritten 54 | if variable_name in ipython.user_ns: 55 | variable_name = incrementing_label(variable_name, ipython.user_ns) 56 | logger.debug(f"assigning {len(sampled_df)}-row dataframe to `{variable_name}` in {ipython}") 57 | ipython.user_ns[variable_name] = sampled_df 58 | -------------------------------------------------------------------------------- /src/dx/comms/resample.py: -------------------------------------------------------------------------------- 1 | import structlog 2 | 3 | from dx.filtering import handle_resample 4 | from dx.types.filters import DEXResampleMessage 5 | 6 | logger = structlog.get_logger(__name__) 7 | 8 | 9 | # ref: https://jupyter-notebook.readthedocs.io/en/stable/comms.html#opening-a-comm-from-the-frontend 10 | def resampler(comm, open_msg): 11 | """ 12 | Datalink resample request. 13 | """ 14 | 15 | @comm.on_msg 16 | def _recv(msg): 17 | handle_resample_comm(msg) 18 | comm.send({"status": "success", "source": "resampler"}) 19 | 20 | comm.send({"status": "connected", "source": "resampler"}) 21 | 22 | 23 | def handle_resample_comm(msg): 24 | data = msg.get("content", {}).get("data", {}) 25 | if not data: 26 | return 27 | 28 | logger.debug(f"handling resample {msg=}") 29 | msg = DEXResampleMessage.parse_obj(data) 30 | handle_resample(msg) 31 | -------------------------------------------------------------------------------- /src/dx/datatypes/__init__.py: -------------------------------------------------------------------------------- 1 | from dx.datatypes.date_time import * 2 | from dx.datatypes.geometry import * 3 | from dx.datatypes.main import * 4 | from dx.datatypes.misc import * 5 | from dx.datatypes.numeric import * 6 | from dx.datatypes.text import * 7 | -------------------------------------------------------------------------------- /src/dx/datatypes/numeric.py: -------------------------------------------------------------------------------- 1 | import random 2 | from decimal import Decimal 3 | 4 | import numpy as np 5 | import pandas as pd 6 | import structlog 7 | 8 | logger = structlog.get_logger(__name__) 9 | 10 | __all__ = [ 11 | "generate_integer_series", 12 | "generate_float_series", 13 | "generate_decimal_series", 14 | "generate_complex_number_series", 15 | ] 16 | 17 | 18 | ### Generator helper functions ### 19 | def generate_integer_series( 20 | num_rows: int, value_min: int = -100, value_max: int = 100 21 | ) -> pd.Series: 22 | """ 23 | Generate a series of random `integer` values. 24 | 25 | Parameters 26 | ---------- 27 | num_rows: int 28 | Number of rows to generate 29 | value_min: int 30 | Minimum value to generate 31 | value_max: int 32 | Maximum value to generate 33 | """ 34 | return pd.Series([np.random.randint(value_min, value_max) for _ in range(num_rows)]) 35 | 36 | 37 | def generate_float_series(num_rows: int, value_min: int = 0, value_max: int = 0) -> pd.Series: 38 | """ 39 | Generate a series of random `float` values. 40 | (`value_min` and `value_max` both set to `0` by default since a random `0.0`-`1.0` is added.) 41 | 42 | Parameters 43 | ---------- 44 | num_rows: int 45 | Number of rows to generate 46 | value_min: int 47 | Minimum value to generate 48 | value_max: int 49 | Maximum value to generate 50 | """ 51 | return pd.Series( 52 | [random.randint(value_min, value_max) + np.random.rand() for _ in range(num_rows)] 53 | ) 54 | 55 | 56 | def generate_decimal_series(num_rows: int, value_min: int = 0, value_max: int = 0) -> pd.Series: 57 | """ 58 | Generate a series of random `decimal.Decimal` values. 59 | 60 | Parameters 61 | ---------- 62 | num_rows: int 63 | Number of rows to generate 64 | value_min: int 65 | Minimum value to generate 66 | value_max: int 67 | Maximum value to generate 68 | """ 69 | return generate_float_series(num_rows, value_min, value_max).apply(lambda x: Decimal(x)) 70 | 71 | 72 | def generate_complex_number_series(num_rows: int) -> pd.Series: 73 | """ 74 | Generate a series of random complex numbers. 75 | 76 | Parameters 77 | ---------- 78 | num_rows: int 79 | Number of rows to generate 80 | """ 81 | return pd.Series( 82 | [complex(real=np.random.rand(), imag=np.random.rand()) for _ in range(num_rows)] 83 | ) 84 | 85 | 86 | ### Handler helper functions ### 87 | def handle_complex_number_series(s: pd.Series) -> pd.Series: 88 | types = (complex,) 89 | if any(isinstance(v, types) for v in s.dropna().head().values): 90 | logger.debug(f"series `{s.name}` has complex numbers; converting to real/imag string") 91 | s = s.apply(lambda x: f"{x.real}+{x.imag}j" if isinstance(x, types) else x) 92 | return s 93 | 94 | 95 | def handle_decimal_series(s: pd.Series) -> pd.Series: 96 | types = (Decimal,) 97 | if any(isinstance(v, types) for v in s.dropna().head().values): 98 | logger.debug(f"series `{s.name}` has Decimals; converting to float") 99 | s = s.astype(float) 100 | return s 101 | -------------------------------------------------------------------------------- /src/dx/datatypes/text.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | 4 | import numpy as np 5 | import pandas as pd 6 | import structlog 7 | 8 | try: 9 | from faker import Faker 10 | 11 | fake = Faker() 12 | FAKER_INSTALLED = True 13 | except ImportError: 14 | FAKER_INSTALLED = False 15 | 16 | 17 | logger = structlog.get_logger(__name__) 18 | 19 | __all__ = [ 20 | "generate_text_series", 21 | "generate_keyword_series", 22 | ] 23 | 24 | 25 | def generate_text_series(num_rows: int) -> pd.Series: 26 | """ 27 | Generate a series of random long `str` values. (Requires `faker` to be installed) 28 | 29 | Parameters 30 | ---------- 31 | num_rows: int 32 | Number of rows to generate 33 | """ 34 | if not FAKER_INSTALLED: 35 | logger.warning("faker is not installed, skipping text_column") 36 | return np.nan 37 | 38 | return pd.Series([fake.text() for _ in range(num_rows)]) 39 | 40 | 41 | def generate_keyword_series(num_rows: int, num_letters: int = 2) -> pd.Series: 42 | """ 43 | Generate a series of random short `str` values. 44 | 45 | Parameters 46 | ---------- 47 | num_rows: int 48 | Number of rows to generate 49 | num_letters: int 50 | Number of letters to use in each keyword 51 | """ 52 | return pd.Series( 53 | ["".join(random.sample(string.ascii_uppercase, num_letters)) for _ in range(num_rows)] 54 | ) 55 | -------------------------------------------------------------------------------- /src/dx/dependencies.py: -------------------------------------------------------------------------------- 1 | from importlib.util import find_spec 2 | 3 | import pandas as pd 4 | import structlog 5 | 6 | logger = structlog.get_logger(__name__) 7 | 8 | 9 | def package_installed(package_name) -> bool: 10 | package_exists = find_spec(package_name) is not None 11 | return package_exists 12 | 13 | 14 | def dask_installed(): 15 | return package_installed("dask") 16 | 17 | 18 | def geopandas_installed(): 19 | return package_installed("geopandas") 20 | 21 | 22 | def modin_installed(): 23 | return package_installed("modin") 24 | 25 | 26 | def polars_installed(): 27 | return package_installed("polars") 28 | 29 | 30 | def vaex_installed(): 31 | return package_installed("vaex") 32 | 33 | 34 | def get_default_renderable_types() -> dict: 35 | """Return a dictionary of default renderable types, 36 | including callable functions or names of methods to use 37 | to convert a specific type to a pandas.DataFrame. 38 | """ 39 | types = { 40 | pd.Series: None, 41 | pd.DataFrame: None, 42 | } 43 | 44 | if dask_installed(): 45 | import dask.dataframe as dd 46 | 47 | dd_types = {dd.Series: "compute", dd.DataFrame: "compute"} 48 | types.update(dd_types) 49 | 50 | if geopandas_installed(): 51 | import geopandas as gpd 52 | 53 | gpd_types = {gpd.GeoDataFrame: None, gpd.GeoSeries: None} 54 | types.update(gpd_types) 55 | 56 | if modin_installed(): 57 | import modin.pandas as mpd 58 | 59 | mpd_types = {mpd.DataFrame: "_to_pandas", mpd.Series: "_to_pandas"} 60 | types.update(mpd_types) 61 | 62 | if polars_installed(): 63 | import polars as pl 64 | 65 | pl_types = {pl.DataFrame: "to_pandas", pl.Series: "to_pandas"} 66 | types.update(pl_types) 67 | 68 | if vaex_installed(): 69 | import vaex 70 | 71 | vaex_types = {vaex.dataframe.DataFrame: "to_pandas_df"} 72 | types.update(vaex_types) 73 | 74 | return types 75 | -------------------------------------------------------------------------------- /src/dx/dx.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | from typing import List, Optional, Union 3 | 4 | import pandas as pd 5 | from IPython import display as ipydisplay 6 | from IPython.core.interactiveshell import InteractiveShell 7 | 8 | from dx.formatters.main import handle_format 9 | from dx.settings import settings_context 10 | from dx.types.main import DXDisplayMode 11 | 12 | 13 | def display( 14 | data: Union[List[dict], pd.DataFrame, Union[pathlib.Path, str]], 15 | mode: DXDisplayMode = DXDisplayMode.simple, 16 | ipython_shell: Optional[InteractiveShell] = None, 17 | **kwargs, 18 | ) -> None: 19 | """ 20 | Display a single object with the DX display format. 21 | (e.g. pd.DataFrame, .csv/.json filepath, or tabular dataset) 22 | """ 23 | 24 | if isinstance(data, str): 25 | path = pathlib.PurePosixPath(data) 26 | if path.suffix == ".csv": 27 | data = pd.read_csv(data) 28 | elif path.suffix == ".json": 29 | data = pd.read_json(data) 30 | else: 31 | raise ValueError(f"Unsupported file type: `{path.suffix}`") 32 | 33 | df = pd.DataFrame(data) 34 | with settings_context(display_mode=mode, ipython_shell=ipython_shell): 35 | handle_format(df, **kwargs) 36 | 37 | 38 | def show_docs( 39 | src: str = "https://noteable-io.github.io/dx/", 40 | width: str = "100%", 41 | height: str = "400px", 42 | ) -> None: 43 | """Renders the dx documentation in an IFrame for use in a notebook environment.""" 44 | docs_iframe = ipydisplay.IFrame(src=src, width=width, height=height) 45 | ipydisplay.display(docs_iframe) 46 | -------------------------------------------------------------------------------- /src/dx/formatters/__init__.py: -------------------------------------------------------------------------------- 1 | from dx.formatters.enhanced import register 2 | from dx.formatters.main import handle_format 3 | from dx.formatters.plain import reset 4 | from dx.formatters.simple import deregister 5 | -------------------------------------------------------------------------------- /src/dx/formatters/enhanced.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | from typing import Optional 3 | 4 | from IPython import get_ipython 5 | from IPython.core.interactiveshell import InteractiveShell 6 | from pydantic import BaseSettings, Field 7 | 8 | from dx.formatters.main import DEFAULT_IPYTHON_DISPLAY_FORMATTER, DXDisplayFormatter 9 | from dx.settings import get_settings 10 | 11 | settings = get_settings() 12 | 13 | 14 | class DXSettings(BaseSettings): 15 | DX_DISPLAY_MAX_ROWS: int = 50_000 16 | DX_DISPLAY_MAX_COLUMNS: int = 50 17 | DX_MAX_STRING_LENGTH: int = 250 18 | DX_HTML_TABLE_SCHEMA: bool = Field(True, allow_mutation=False) 19 | DX_MEDIA_TYPE: str = Field("application/vnd.dex.v1+json", allow_mutation=False) 20 | 21 | DX_FLATTEN_INDEX_VALUES: bool = False 22 | DX_FLATTEN_COLUMN_VALUES: bool = True 23 | DX_STRINGIFY_INDEX_VALUES: bool = False 24 | DX_STRINGIFY_COLUMN_VALUES: bool = True 25 | 26 | class Config: 27 | validate_assignment = True # we need this to enforce `allow_mutation` 28 | json_encoders = {type: lambda t: str(t)} 29 | 30 | 31 | @lru_cache 32 | def get_dx_settings(): 33 | return DXSettings() 34 | 35 | 36 | dx_settings = get_dx_settings() 37 | 38 | 39 | def register(ipython_shell: Optional[InteractiveShell] = None) -> None: 40 | """ 41 | Enables the DEX media type output display formatting and 42 | updates global dx & pandas settings with DX settings. 43 | """ 44 | if get_ipython() is None and ipython_shell is None: 45 | return 46 | 47 | global settings 48 | settings.DISPLAY_MODE = "enhanced" 49 | 50 | settings_to_apply = { 51 | "DISPLAY_MAX_COLUMNS", 52 | "DISPLAY_MAX_ROWS", 53 | "MAX_STRING_LENGTH", 54 | "MEDIA_TYPE", 55 | "FLATTEN_INDEX_VALUES", 56 | "FLATTEN_COLUMN_VALUES", 57 | "STRINGIFY_INDEX_VALUES", 58 | "STRINGIFY_COLUMN_VALUES", 59 | } 60 | for setting in settings_to_apply: 61 | val = getattr(dx_settings, f"DX_{setting}", None) 62 | setattr(settings, setting, val) 63 | 64 | ipython = ipython_shell or get_ipython() 65 | custom_formatter = DXDisplayFormatter() 66 | custom_formatter.formatters = DEFAULT_IPYTHON_DISPLAY_FORMATTER.formatters 67 | ipython.display_formatter = custom_formatter 68 | -------------------------------------------------------------------------------- /src/dx/formatters/plain.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from functools import lru_cache 3 | from typing import Optional 4 | 5 | import pandas as pd 6 | import structlog 7 | from IPython import get_ipython 8 | from IPython.core.interactiveshell import InteractiveShell 9 | from pydantic import BaseSettings, Field 10 | 11 | from dx.formatters.main import DEFAULT_IPYTHON_DISPLAY_FORMATTER 12 | from dx.settings import get_settings 13 | 14 | logger = structlog.get_logger(__name__) 15 | settings = get_settings() 16 | 17 | warnings.filterwarnings("ignore") 18 | 19 | 20 | class PandasSettings(BaseSettings): 21 | # "plain" (pandas) display mode 22 | PANDAS_DISPLAY_MAX_ROWS: int = 60 23 | PANDAS_DISPLAY_MAX_COLUMNS: int = 20 24 | PANDAS_MAX_STRING_LENGTH: int = 50 25 | PANDAS_HTML_TABLE_SCHEMA: bool = Field(False, allow_mutation=False) 26 | PANDAS_MEDIA_TYPE: str = Field("application/vnd.dataresource+json", allow_mutation=False) 27 | 28 | class Config: 29 | validate_assignment = True # we need this to enforce `allow_mutation` 30 | json_encoders = {type: lambda t: str(t)} 31 | 32 | 33 | @lru_cache 34 | def get_pandas_settings(): 35 | return PandasSettings() 36 | 37 | 38 | pandas_settings = get_pandas_settings() 39 | 40 | 41 | def reset(ipython_shell: Optional[InteractiveShell] = None) -> None: 42 | """ 43 | Resets all nteract/Noteable options, reverting to the default 44 | pandas display options and IPython display formatter. 45 | """ 46 | if get_ipython() is None and ipython_shell is None: 47 | return 48 | 49 | global settings 50 | settings.DISPLAY_MODE = "plain" 51 | 52 | settings.DISPLAY_MAX_COLUMNS = pandas_settings.PANDAS_DISPLAY_MAX_COLUMNS 53 | settings.DISPLAY_MAX_ROWS = pandas_settings.PANDAS_DISPLAY_MAX_ROWS 54 | settings.MEDIA_TYPE = pandas_settings.PANDAS_MEDIA_TYPE 55 | 56 | pd.set_option("display.max_columns", pandas_settings.PANDAS_DISPLAY_MAX_COLUMNS) 57 | pd.set_option("display.max_rows", pandas_settings.PANDAS_DISPLAY_MAX_ROWS) 58 | pd.set_option("display.max_colwidth", pandas_settings.PANDAS_MAX_STRING_LENGTH) 59 | 60 | ipython = ipython_shell or get_ipython() 61 | ipython.display_formatter = DEFAULT_IPYTHON_DISPLAY_FORMATTER 62 | -------------------------------------------------------------------------------- /src/dx/formatters/simple.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | from typing import Optional 3 | 4 | from IPython import get_ipython 5 | from IPython.core.interactiveshell import InteractiveShell 6 | from pydantic import BaseSettings, Field 7 | 8 | from dx.formatters.main import DEFAULT_IPYTHON_DISPLAY_FORMATTER, DXDisplayFormatter 9 | from dx.settings import get_settings 10 | 11 | settings = get_settings() 12 | 13 | 14 | class DataResourceSettings(BaseSettings): 15 | # "simple" (classic simpleTable/DEX) display mode 16 | DATARESOURCE_DISPLAY_MAX_ROWS: int = 50_000 17 | DATARESOURCE_DISPLAY_MAX_COLUMNS: int = 50 18 | DATARESOURCE_MAX_STRING_LENGTH: int = 250 19 | DATARESOURCE_HTML_TABLE_SCHEMA: bool = Field(True, allow_mutation=False) 20 | DATARESOURCE_MEDIA_TYPE: str = Field("application/vnd.dataresource+json", allow_mutation=False) 21 | 22 | DATARESOURCE_FLATTEN_INDEX_VALUES: bool = False 23 | DATARESOURCE_FLATTEN_COLUMN_VALUES: bool = True 24 | DATARESOURCE_STRINGIFY_INDEX_VALUES: bool = False 25 | DATARESOURCE_STRINGIFY_COLUMN_VALUES: bool = True 26 | 27 | class Config: 28 | validate_assignment = True # we need this to enforce `allow_mutation` 29 | json_encoders = {type: lambda t: str(t)} 30 | 31 | 32 | @lru_cache 33 | def get_dataresource_settings(): 34 | return DataResourceSettings() 35 | 36 | 37 | dataresource_settings = get_dataresource_settings() 38 | 39 | 40 | def deregister(ipython_shell: Optional[InteractiveShell] = None) -> None: 41 | """ 42 | Sets the current IPython display formatter as the dataresource 43 | display formatter, used for simpleTable / "classic DEX" outputs 44 | and updates global dx & pandas settings with dataresource settings. 45 | """ 46 | if get_ipython() is None and ipython_shell is None: 47 | return 48 | 49 | global settings 50 | settings.DISPLAY_MODE = "simple" 51 | 52 | settings_to_apply = { 53 | "DISPLAY_MAX_COLUMNS", 54 | "DISPLAY_MAX_ROWS", 55 | "MAX_STRING_LENGTH", 56 | "MEDIA_TYPE", 57 | "FLATTEN_INDEX_VALUES", 58 | "FLATTEN_COLUMN_VALUES", 59 | "STRINGIFY_INDEX_VALUES", 60 | "STRINGIFY_COLUMN_VALUES", 61 | } 62 | for setting in settings_to_apply: 63 | val = getattr(dataresource_settings, f"DATARESOURCE_{setting}", None) 64 | setattr(settings, setting, val) 65 | 66 | ipython = ipython_shell or get_ipython() 67 | 68 | custom_formatter = DXDisplayFormatter() 69 | custom_formatter.formatters = DEFAULT_IPYTHON_DISPLAY_FORMATTER.formatters 70 | ipython.display_formatter = custom_formatter 71 | -------------------------------------------------------------------------------- /src/dx/formatters/summarizing.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional 2 | 3 | import pandas as pd 4 | 5 | 6 | class DataFrameSummarizer: 7 | _instance: "DataFrameSummarizer" = None 8 | summarizing_func: Optional[Callable] = None 9 | 10 | def __init__(self, summarizing_func: Optional[Callable] = None): 11 | if summarizing_func is None: 12 | self._try_to_load_repr_llm() 13 | else: 14 | self.summarizing_func = summarizing_func 15 | 16 | def _try_to_load_repr_llm(self) -> None: 17 | """Load repr_llm's summarize_dataframe into the summarizing_func if it's available.""" 18 | try: 19 | from repr_llm.pandas import summarize_dataframe 20 | 21 | self.summarizing_func = summarize_dataframe 22 | except ImportError: 23 | return 24 | 25 | @classmethod 26 | def instance(cls) -> "DataFrameSummarizer": 27 | if cls._instance is None: 28 | cls._instance = cls() 29 | return cls._instance 30 | 31 | def summarize(self, df: pd.DataFrame) -> str: 32 | """Generate a summary of a dataframe using the configured summarizing_func.""" 33 | if not isinstance(df, pd.DataFrame): 34 | raise ValueError("`df` must be a pandas DataFrame") 35 | 36 | if self.summarizing_func is None: 37 | return df.describe().to_string() 38 | 39 | return self.summarizing_func(df) 40 | 41 | 42 | def get_summarizing_function() -> Optional[Callable]: 43 | """Get the function to use for summarizing dataframes.""" 44 | return DataFrameSummarizer.instance().summarizing_func 45 | 46 | 47 | def set_summarizing_function(func: Callable) -> None: 48 | """Set the function to use for summarizing dataframes.""" 49 | DataFrameSummarizer.instance().summarizing_func = func 50 | 51 | 52 | def make_df_summary(df: pd.DataFrame) -> str: 53 | """Generate a summary of a dataframe using the configured summarizing_func.""" 54 | return DataFrameSummarizer.instance().summarize(df) 55 | -------------------------------------------------------------------------------- /src/dx/loggers.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import logging.config 3 | import sys 4 | from typing import Optional 5 | 6 | import structlog 7 | 8 | from dx.settings import get_settings 9 | 10 | settings = get_settings() 11 | 12 | # Timestamp format applied to both vanilla and structlog messages 13 | timestamper = structlog.processors.TimeStamper(fmt=settings.DATETIME_STRING_FORMAT) 14 | 15 | # Pre-processing for Vanilla Log messages 16 | pre_chain = [ 17 | # Add extra attributes of LogRecord objects to the event dictionary 18 | # so that values passed in the extra parameter of log methods pass 19 | # through to log output. 20 | structlog.stdlib.ExtraAdder(), 21 | ] 22 | 23 | # Pre-processing for Structlog messages 24 | structlog.configure( 25 | processors=[ 26 | structlog.stdlib.PositionalArgumentsFormatter(), 27 | structlog.processors.StackInfoRenderer(), 28 | structlog.processors.format_exc_info, 29 | structlog.stdlib.ProcessorFormatter.wrap_for_formatter, 30 | ], 31 | logger_factory=structlog.stdlib.LoggerFactory(), 32 | wrapper_class=structlog.stdlib.BoundLogger, 33 | cache_logger_on_first_use=True, 34 | ) 35 | 36 | 37 | # List of processors to be applied after pre-processing both vanilla 38 | # and structlog messages, but before a final processor that formats 39 | # the logs into JSON format or colored terminal output. 40 | shared_processors = [ 41 | # log level / logger name, effects coloring in ConsoleRenderer(colors=True) 42 | structlog.stdlib.add_log_level, 43 | structlog.stdlib.add_logger_name, 44 | # timestamp format 45 | timestamper, 46 | # To see all CallsiteParameterAdder options: 47 | # https://www.structlog.org/en/stable/api.html?highlight=CallsiteParameterAdder#structlog.processors.CallsiteParameterAdder 48 | # more options include module, pathname, process, process_name, thread, thread_name 49 | structlog.processors.CallsiteParameterAdder( 50 | { 51 | structlog.processors.CallsiteParameter.FILENAME, 52 | structlog.processors.CallsiteParameter.FUNC_NAME, 53 | structlog.processors.CallsiteParameter.LINENO, 54 | } 55 | ), 56 | # Any structlog.contextvars.bind_contextvars included in middleware/functions 57 | structlog.contextvars.merge_contextvars, 58 | # strip _record and _from_structlog keys from event dictionary 59 | structlog.stdlib.ProcessorFormatter.remove_processors_meta, 60 | ] 61 | 62 | 63 | def configure_logging(app_level: Optional[int] = None): 64 | logging.config.dictConfig( 65 | { 66 | "version": 1, 67 | "disable_existing_loggers": False, 68 | "formatters": { 69 | "color": { 70 | "()": structlog.stdlib.ProcessorFormatter, 71 | "processors": shared_processors 72 | + [ 73 | structlog.dev.ConsoleRenderer(colors=True), 74 | ], 75 | "foreign_pre_chain": pre_chain, 76 | }, 77 | }, 78 | "handlers": { 79 | "default": { 80 | "class": "logging.StreamHandler", 81 | "formatter": "color", 82 | "stream": sys.stdout, 83 | }, 84 | }, 85 | "loggers": { 86 | "dx": { 87 | "handlers": ["default"], 88 | "level": "WARNING", 89 | "propagate": True, 90 | }, 91 | }, 92 | } 93 | ) 94 | # Example of setting one specific logger at a level lower than loggers config 95 | logging.getLogger("dx").setLevel(app_level or settings.LOG_LEVEL) 96 | -------------------------------------------------------------------------------- /src/dx/plotting/__init__.py: -------------------------------------------------------------------------------- 1 | from dx.plotting.dex import * 2 | from dx.plotting.main import * 3 | -------------------------------------------------------------------------------- /src/dx/plotting/dashboards.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, Union 2 | 3 | import pandas as pd 4 | import structlog 5 | 6 | from dx.formatters.main import handle_format 7 | from dx.plotting import dex 8 | from dx.settings import settings_context 9 | from dx.types.dex_metadata import DEXMetadata, DEXView 10 | 11 | logger = structlog.get_logger(__name__) 12 | 13 | 14 | def dashboard( 15 | df: pd.DataFrame, 16 | views: List[Union[str, dict, list, DEXView]], 17 | **kwargs, 18 | ) -> Optional[DEXMetadata]: 19 | """ 20 | Creates and renders a DEX dashboard from a list of views. 21 | 22 | Parameters 23 | ---------- 24 | df: pd.DataFrame 25 | The dataframe to be rendered. 26 | views: List[Union[str, dict, list, DEXView]] 27 | A list of views to be created and rendered in the dashboard. 28 | By default, each item in the list will be treated as a row item. 29 | Each item in the list can be another nested list of views to 30 | determine column positioning within the dashboard view. 31 | """ 32 | dex_metadata = DEXMetadata() 33 | multiview_order = [] 34 | 35 | # generate and add views, assuming a matrix-like structure 36 | for row_num, views_row in enumerate(views): 37 | if not isinstance(views_row, list): 38 | views_row = [views_row] 39 | for col_num, view in enumerate(views_row): 40 | if isinstance(view, str): 41 | # get the direct-to-chart option since we can't pass kwargs 42 | view = dex.get_chart_view(df, f"sample_{view.lower()}") 43 | elif isinstance(view, dict): 44 | # view dict 45 | dex_view = DEXView.parse_obj(view) 46 | elif isinstance(view, DEXView): 47 | dex_view = view 48 | else: 49 | raise ValueError(f"Invalid view type: {type(view)}") 50 | 51 | # make the views available for reference 52 | dex_dashboard_view = dex_view.copy( 53 | update={"is_default": False, "chartMode": dex_view.chart_mode} 54 | ) 55 | # TODO: make DEXMetadata.add_view() support adding DEXView 56 | # instead of just dictionaries 57 | dex_dashboard_view_dict = dex_dashboard_view.dict( 58 | by_alias=True, 59 | exclude_none=True, 60 | ) 61 | logger.debug(f"{dex_dashboard_view_dict=}") 62 | dex_metadata.views.append(dex_dashboard_view_dict) 63 | # define the view positioning 64 | multiview = { 65 | "column": col_num, 66 | "row": row_num, 67 | "id": str(dex_dashboard_view.id), 68 | } 69 | multiview_order.append(multiview) 70 | 71 | dashboard_view_metadata = { 72 | "views": multiview_order, 73 | "isDefault": True, 74 | "decoration": { 75 | "title": "🤔 dx dashboard", 76 | }, 77 | } 78 | dashboard_view_metadata.update(kwargs) 79 | 80 | dex_dashboard_metadata = DEXView.parse_obj(dashboard_view_metadata) 81 | 82 | with settings_context(generate_dex_metadata=True): 83 | handle_format( 84 | df, 85 | extra_metadata={ 86 | "dashboard": { 87 | "multiViews": [ 88 | dex_dashboard_metadata.dict(by_alias=True), 89 | ], 90 | }, 91 | "views": dex_metadata.views, 92 | }, 93 | ) 94 | -------------------------------------------------------------------------------- /src/dx/plotting/dex/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from dx.plotting.dex.basic_charts import * 4 | from dx.plotting.dex.comparison_charts import * 5 | from dx.plotting.dex.funnel_charts import * 6 | from dx.plotting.dex.map_charts import * 7 | from dx.plotting.dex.part_to_whole_charts import * 8 | from dx.plotting.dex.relationship_charts import * 9 | from dx.plotting.dex.summary_charts import * 10 | from dx.plotting.dex.time_series_charts import * 11 | from dx.types.dex_metadata import DEXView 12 | 13 | chart_functions = { 14 | **basic_chart_functions(), 15 | **comparison_chart_functions(), 16 | **funnel_chart_functions(), 17 | **maps_chart_functions(), 18 | **part_to_whole_chart_functions(), 19 | **relationship_chart_functions(), 20 | **summary_chart_functions(), 21 | **time_series_chart_functions(), 22 | } 23 | 24 | 25 | def get_chart_view(df, chart_mode: str, **kwargs) -> Optional[DEXView]: 26 | chart_func = chart_functions.get(chart_mode) 27 | if chart_func is not None: 28 | return chart_func(df, return_view=True, **kwargs) 29 | -------------------------------------------------------------------------------- /src/dx/plotting/dex/funnel_charts.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Dict, Optional 2 | 3 | from dx.plotting.utils import handle_view 4 | from dx.types.charts.flow_diagram import DEXFlowDiagramChartView 5 | from dx.types.charts.funnel import DEXFunnelChartView 6 | from dx.types.charts.funnel_chart import DEXFunnelChartChartView 7 | from dx.types.charts.funnel_sunburst import DEXFunnelSunburstChartView 8 | from dx.types.charts.funnel_tree import DEXFunnelTreeChartView 9 | 10 | __all__ = [ 11 | "flow_diagram", 12 | "funnel", 13 | "funnel_chart", 14 | "funnel_sunburst", 15 | "funnel_tree", 16 | "funnel_chart_functions", 17 | ] 18 | 19 | 20 | def sample_flow_diagram(df, **kwargs) -> Optional[DEXFlowDiagramChartView]: 21 | return handle_view(df, chart_mode="flow_diagram", **kwargs) 22 | 23 | 24 | def flow_diagram(df, **kwargs) -> Optional[DEXFlowDiagramChartView]: 25 | # TODO: define user-facing arguments and add documentation 26 | return sample_flow_diagram(df, **kwargs) 27 | 28 | 29 | def sample_funnel_chart(df, **kwargs) -> Optional[DEXFunnelChartChartView]: 30 | return handle_view(df, chart_mode="funnel_chart", **kwargs) 31 | 32 | 33 | def funnel_chart(df, **kwargs) -> Optional[DEXFunnelChartChartView]: 34 | # TODO: define user-facing arguments and add documentation 35 | return sample_funnel_chart(df, **kwargs) 36 | 37 | 38 | def sample_funnel_sunburst(df, **kwargs) -> Optional[DEXFunnelSunburstChartView]: 39 | return handle_view(df, chart_mode="funnel_sunburst", **kwargs) 40 | 41 | 42 | def funnel_sunburst(df, **kwargs) -> Optional[DEXFunnelSunburstChartView]: 43 | # TODO: define user-facing arguments and add documentation 44 | return sample_funnel_sunburst(df, **kwargs) 45 | 46 | 47 | def sample_funnel_tree(df, **kwargs) -> Optional[DEXFunnelTreeChartView]: 48 | return handle_view(df, chart_mode="funnel_tree", **kwargs) 49 | 50 | 51 | def funnel_tree(df, **kwargs) -> Optional[DEXFunnelTreeChartView]: 52 | # TODO: define user-facing arguments and add documentation 53 | return sample_funnel_tree(df, **kwargs) 54 | 55 | 56 | def sample_funnel(df, **kwargs) -> Optional[DEXFunnelChartView]: 57 | return handle_view(df, chart_mode="funnel", **kwargs) 58 | 59 | 60 | def funnel(df, **kwargs) -> Optional[DEXFunnelChartView]: 61 | # TODO: define user-facing arguments and add documentation 62 | return sample_funnel(df, **kwargs) 63 | 64 | 65 | def funnel_chart_functions() -> Dict[str, Callable]: 66 | return { 67 | "flow_diagram": flow_diagram, 68 | "funnel": funnel, 69 | "funnel_chart": funnel_chart, 70 | "funnel_sunburst": funnel_sunburst, 71 | "funnel_tree": funnel_tree, 72 | } 73 | -------------------------------------------------------------------------------- /src/dx/plotting/dex/part_to_whole_charts.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Dict, Optional 2 | 3 | from dx.plotting.utils import handle_view 4 | from dx.types.charts.donut import DEXDonutChartView 5 | from dx.types.charts.partition import DEXPartitionChartView 6 | from dx.types.charts.sunburst import DEXSunburstChartView 7 | from dx.types.charts.treemap import DEXTreemapChartView 8 | 9 | __all__ = [ 10 | "donut", 11 | "partition", 12 | "sunburst", 13 | "treemap", 14 | "part_to_whole_chart_functions", 15 | ] 16 | 17 | 18 | def sample_donut(df, **kwargs) -> Optional[DEXDonutChartView]: 19 | return handle_view(df, chart_mode="donut", **kwargs) 20 | 21 | 22 | def donut(df, **kwargs) -> Optional[DEXDonutChartView]: 23 | # TODO: define user-facing arguments and add documentation 24 | return sample_donut(df, **kwargs) 25 | 26 | 27 | def sample_partition(df, **kwargs) -> Optional[DEXPartitionChartView]: 28 | return handle_view(df, chart_mode="partition", **kwargs) 29 | 30 | 31 | def partition(df, **kwargs) -> Optional[DEXPartitionChartView]: 32 | # TODO: define user-facing arguments and add documentation 33 | return sample_partition(df, **kwargs) 34 | 35 | 36 | def sample_sunburst(df, **kwargs) -> Optional[DEXSunburstChartView]: 37 | return handle_view(df, chart_mode="sunburst", **kwargs) 38 | 39 | 40 | def sunburst(df, **kwargs) -> Optional[DEXSunburstChartView]: 41 | # TODO: define user-facing arguments and add documentation 42 | return sample_sunburst(df, **kwargs) 43 | 44 | 45 | def sample_treemap(df, **kwargs) -> Optional[DEXTreemapChartView]: 46 | return handle_view(df, chart_mode="treemap", **kwargs) 47 | 48 | 49 | def treemap(df, **kwargs) -> Optional[DEXTreemapChartView]: 50 | # TODO: define user-facing arguments and add documentation 51 | return sample_treemap(df, **kwargs) 52 | 53 | 54 | def part_to_whole_chart_functions() -> Dict[str, Callable]: 55 | return { 56 | "donut": donut, 57 | "partition": partition, 58 | "sunburst": sunburst, 59 | "treemap": treemap, 60 | } 61 | -------------------------------------------------------------------------------- /src/dx/plotting/dex/relationship_charts.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Dict, Optional 2 | 3 | from dx.plotting.utils import handle_view 4 | from dx.types.charts.adjacency_matrix import DEXAdjacencyMatrixChartView 5 | from dx.types.charts.arc_flow import DEXArcFlowChartView 6 | from dx.types.charts.dendrogram import DEXDendrogramChartView 7 | from dx.types.charts.force_directed_network import DEXForceDirectedNetworkChartView 8 | from dx.types.charts.sankey import DEXSankeyChartView 9 | 10 | __all__ = [ 11 | "adjacency_matrix", 12 | "arc_flow", 13 | "dendrogram", 14 | "force_directed_network", 15 | "sankey", 16 | "relationship_chart_functions", 17 | ] 18 | 19 | 20 | def sample_adjacency_matrix(df, **kwargs) -> Optional[DEXAdjacencyMatrixChartView]: 21 | return handle_view(df, chart_mode="adjacency_matrix", **kwargs) 22 | 23 | 24 | def adjacency_matrix(df, **kwargs) -> Optional[DEXAdjacencyMatrixChartView]: 25 | # TODO: define user-facing arguments and add documentation 26 | return sample_adjacency_matrix(df, **kwargs) 27 | 28 | 29 | def sample_arc_flow(df, **kwargs) -> Optional[DEXArcFlowChartView]: 30 | return handle_view(df, chart_mode="arc_flow", **kwargs) 31 | 32 | 33 | def arc_flow(df, **kwargs) -> Optional[DEXArcFlowChartView]: 34 | # TODO: define user-facing arguments and add documentation 35 | return sample_arc_flow(df, **kwargs) 36 | 37 | 38 | def sample_dendrogram(df, **kwargs) -> Optional[DEXDendrogramChartView]: 39 | return handle_view(df, chart_mode="dendrogram", **kwargs) 40 | 41 | 42 | def dendrogram(df, **kwargs) -> Optional[DEXDendrogramChartView]: 43 | # TODO: define user-facing arguments and add documentation 44 | return sample_dendrogram(df, **kwargs) 45 | 46 | 47 | def sample_force_directed_network(df, **kwargs) -> Optional[DEXForceDirectedNetworkChartView]: 48 | return handle_view(df, chart_mode="network", chart={"network_type": "force"}, **kwargs) 49 | 50 | 51 | def force_directed_network(df, **kwargs) -> Optional[DEXForceDirectedNetworkChartView]: 52 | # TODO: define user-facing arguments and add documentation 53 | return sample_force_directed_network(df, **kwargs) 54 | 55 | 56 | def sample_sankey(df, **kwargs) -> Optional[DEXSankeyChartView]: 57 | return handle_view(df, chart_mode="sankey", **kwargs) 58 | 59 | 60 | def sankey(df, **kwargs) -> Optional[DEXSankeyChartView]: 61 | # TODO: define user-facing arguments and add documentation 62 | return sample_sankey(df, **kwargs) 63 | 64 | 65 | def relationship_chart_functions() -> Dict[str, Callable]: 66 | return { 67 | "adjacency_matrix": adjacency_matrix, 68 | "arc_flow": arc_flow, 69 | "dendrogram": dendrogram, 70 | "force_directed_network": force_directed_network, 71 | "sankey": sankey, 72 | } 73 | -------------------------------------------------------------------------------- /src/dx/plotting/dex/time_series_charts.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Dict, Optional 2 | 3 | from dx.plotting.utils import handle_view 4 | from dx.types.charts.candlestick import DEXCandlestickChartView 5 | from dx.types.charts.cumulative import DEXCumulativeChartView 6 | from dx.types.charts.line_percent import DEXLinePercentChartView 7 | from dx.types.charts.stacked_area import DEXStackedAreaChartView 8 | from dx.types.charts.stacked_percent import DEXStackedPercentChartView 9 | 10 | __all__ = [ 11 | "candlestick", 12 | "cumulative", 13 | "line_percent", 14 | "stacked_area", 15 | "stacked_percent", 16 | "time_series_chart_functions", 17 | ] 18 | 19 | 20 | def sample_candlestick(df, **kwargs) -> Optional[DEXCandlestickChartView]: 21 | return handle_view(df, chart_mode="candlestick", **kwargs) 22 | 23 | 24 | def candlestick(df, **kwargs) -> Optional[DEXCandlestickChartView]: 25 | # TODO: define user-facing arguments and add documentation 26 | return sample_candlestick(df, **kwargs) 27 | 28 | 29 | def sample_cumulative(df, **kwargs) -> Optional[DEXCumulativeChartView]: 30 | return handle_view(df, chart_mode="cumulative", **kwargs) 31 | 32 | 33 | def cumulative(df, **kwargs) -> Optional[DEXCumulativeChartView]: 34 | # TODO: define user-facing arguments and add documentation 35 | return sample_cumulative(df, **kwargs) 36 | 37 | 38 | def sample_line_percent(df, **kwargs) -> Optional[DEXLinePercentChartView]: 39 | return handle_view(df, chart_mode="line_percent", **kwargs) 40 | 41 | 42 | def line_percent(df, **kwargs) -> Optional[DEXLinePercentChartView]: 43 | # TODO: define user-facing arguments and add documentation 44 | return sample_line_percent(df, **kwargs) 45 | 46 | 47 | def sample_stacked_area(df, **kwargs) -> Optional[DEXStackedAreaChartView]: 48 | return handle_view(df, chart_mode="stacked_area", **kwargs) 49 | 50 | 51 | def stacked_area(df, **kwargs) -> Optional[DEXStackedAreaChartView]: 52 | # TODO: define user-facing arguments and add documentation 53 | return sample_stacked_area(df, **kwargs) 54 | 55 | 56 | def sample_stacked_percent(df, **kwargs) -> Optional[DEXStackedPercentChartView]: 57 | return handle_view(df, chart_mode="stacked_percent", **kwargs) 58 | 59 | 60 | def stacked_percent(df, **kwargs) -> Optional[DEXStackedPercentChartView]: 61 | # TODO: define user-facing arguments and add documentation 62 | return sample_stacked_percent(df, **kwargs) 63 | 64 | 65 | def time_series_chart_functions() -> Dict[str, Callable]: 66 | return { 67 | "candlestick": candlestick, 68 | "cumulative": cumulative, 69 | "line_percent": line_percent, 70 | "stacked_area": stacked_area, 71 | "stacked_percent": stacked_percent, 72 | } 73 | -------------------------------------------------------------------------------- /src/dx/plotting/main.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import structlog 3 | 4 | from dx.formatters.main import handle_format 5 | from dx.plotting import dex 6 | from dx.plotting.dashboards import dashboard 7 | from dx.settings import get_settings, settings_context 8 | 9 | logger = structlog.get_logger(__name__) 10 | 11 | settings = get_settings() 12 | 13 | __all__ = [ 14 | "enable_plotting_backend", 15 | "disable_plotting_backend", 16 | "plot", 17 | ] 18 | 19 | 20 | def enable_plotting_backend(): 21 | """ 22 | Convenience toggle for enabling the dx plotting backend for pandas. 23 | """ 24 | pd.options.plotting.backend = "dx" 25 | 26 | 27 | def disable_plotting_backend(): 28 | """ 29 | Convenience toggle for disabling the dx plotting backend for pandas and reverting to matplotlib. 30 | """ 31 | pd.options.plotting.backend = "matplotlib" 32 | 33 | 34 | def plot(df: dict, kind: str, **kwargs) -> None: 35 | """Main plotting backend for dx. `kind` must be passed through to determine the appropriate 36 | chart type ("line" by default). 37 | 38 | ref: https://github.com/plotly/plotly.py/blob/master/packages/python/plotly/plotly/__init__.py 39 | """ 40 | view = dex.get_chart_view(df, kind, **kwargs) 41 | if view: 42 | pass 43 | elif kind == "dashboard": 44 | return dashboard(df, **kwargs) 45 | else: 46 | raise NotImplementedError(f"{kind=} not yet supported for plotting.backend='dx'") 47 | 48 | view_metadata = view.dict( 49 | exclude_unset=True, 50 | exclude_none=True, 51 | by_alias=True, 52 | ) 53 | logger.debug(f"{view_metadata=}") 54 | 55 | with settings_context(generate_dex_metadata=True): 56 | # if someone is calling one of these functions with the dx plotting backend, 57 | # there isn't any way around persisting frontend-generated metadata, 58 | # so anything existing will be replaced with the new metadata here 59 | handle_format(df, extra_metadata=view_metadata) 60 | -------------------------------------------------------------------------------- /src/dx/plotting/utils.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | import pandas as pd 4 | import structlog 5 | from pydantic import parse_obj_as 6 | 7 | from dx.formatters.main import handle_format 8 | from dx.settings import settings_context 9 | from dx.types.charts import dex_charts 10 | from dx.types.charts._base import DEXChartBase 11 | from dx.types.dex_metadata import DEXView 12 | 13 | logger = structlog.get_logger(__name__) 14 | 15 | 16 | def handle_view( 17 | df: pd.DataFrame, 18 | chart_mode: str, 19 | chart: Optional[dict] = None, 20 | return_view: bool = False, 21 | **kwargs, 22 | ) -> Optional[DEXView]: 23 | """ 24 | Primary function that takes a DataFrame and chart information to coerce it into a DEXView after 25 | parsing the chart information into a DEXChartView object. Once modeled, it will either be 26 | handled by the display formatter, or will return the view. 27 | """ 28 | logger.debug(f"{chart=}") 29 | 30 | view_params = { 31 | "chart_mode": chart_mode, 32 | "chart": DEXChartBase.parse_obj(chart or {}), 33 | "decoration": {"title": chart_mode.title()}, 34 | } 35 | view_params.update(kwargs) 36 | 37 | view = parse_obj_as(dex_charts, view_params) 38 | 39 | if view.chart.summary_type is not None: 40 | # show "Summary (Violin)" instead of just "Summary" 41 | view.decoration.title = f"{view.decoration.title} ({view.chart.summary_type})" 42 | 43 | if return_view: 44 | return view 45 | 46 | view_metadata = view.dict( 47 | exclude_unset=True, 48 | exclude_none=True, 49 | by_alias=True, 50 | ) 51 | logger.debug(f"{view_metadata=}") 52 | with settings_context(generate_dex_metadata=True): 53 | handle_format(df, extra_metadata=view_metadata) 54 | 55 | 56 | def raise_for_missing_columns(columns: List[str], existing_columns: pd.Index) -> None: 57 | """ 58 | Checks if a column exists in a dataframe or is "index". 59 | If both fail, this raises a ValueError. 60 | """ 61 | if isinstance(columns, str): 62 | columns = [columns] 63 | 64 | for column in columns: 65 | if str(column) == "index": 66 | return 67 | if column in existing_columns: 68 | return 69 | raise ValueError(f"Column `{column}` not found in DataFrame") 70 | -------------------------------------------------------------------------------- /src/dx/types/__init__.py: -------------------------------------------------------------------------------- 1 | from .charts import * 2 | from .dex_metadata import * 3 | from .filters import * 4 | from .main import * 5 | -------------------------------------------------------------------------------- /src/dx/types/charts/_template.py: -------------------------------------------------------------------------------- 1 | """Template for new Chart types. Do not use this file directly.""" 2 | 3 | 4 | from typing import Literal 5 | 6 | from pydantic import Field 7 | 8 | from dx.types.charts._base import DEXChartBase 9 | from dx.types.dex_metadata import DEXView 10 | 11 | 12 | class DEXTemplateConfig(DEXChartBase): 13 | # see dx.types.charts._base.DEXChartBase for available fields and configs 14 | pass 15 | 16 | 17 | class DEXTemplateChartView(DEXView): 18 | chart_mode: Literal["template"] = "template" 19 | chart: DEXTemplateConfig = Field(default_factory=DEXTemplateConfig) 20 | -------------------------------------------------------------------------------- /src/dx/types/charts/adjacency_matrix.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXAdjacencyMatrixConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXAdjacencyMatrixChartView(DEXView): 15 | chart_mode: Literal["adjacency_matrix"] = "adjacency_matrix" 16 | chart: DEXAdjacencyMatrixConfig = Field(default_factory=DEXAdjacencyMatrixConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/arc_flow.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXArcFlowConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXArcFlowChartView(DEXView): 15 | chart_mode: Literal["arc_flow"] = "arc_flow" 16 | chart: DEXArcFlowConfig = Field(default_factory=DEXArcFlowConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/bar.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXBarChartConfig(DEXChartBase): 10 | class Config: 11 | fields = { 12 | "bar_label": {"include": True}, 13 | "bar_projection": {"include": True}, 14 | "bar_subcategory": {"include": True}, 15 | "combination_mode": {"include": True}, 16 | "dim1": {"include": True}, 17 | "group_other": {"include": True}, 18 | "metric1": {"include": True}, 19 | "metric3": {"include": True}, 20 | "pro_bar_mode": {"include": True}, 21 | "second_bar_metric": {"include": True}, 22 | "second_metric_style": {"include": True}, 23 | "selected_bar_metrics": {"include": True}, 24 | "sort_columns_by": {"include": True}, 25 | } 26 | 27 | 28 | class DEXBarChartView(DEXView): 29 | chart_mode: Literal["bar"] = "bar" 30 | chart: DEXBarChartConfig = Field(default_factory=DEXBarChartConfig) 31 | -------------------------------------------------------------------------------- /src/dx/types/charts/bignumber.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXBigNumberChartConfig(DEXChartBase): 10 | class Config: 11 | fields = { 12 | "dim1": {"include": True}, 13 | "metric1": {"include": True}, 14 | "combination_mode": {"include": True}, 15 | "sparkchart": {"include": True}, 16 | "second_metric": {"include": True}, 17 | "second_metric_comparison": {"include": True}, 18 | } 19 | 20 | 21 | class DEXBigNumberChartView(DEXView): 22 | chart_mode: Literal["bignumber"] = "bignumber" 23 | chart: DEXBigNumberChartConfig = Field(default_factory=DEXBigNumberChartConfig) 24 | -------------------------------------------------------------------------------- /src/dx/types/charts/candlestick.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXCandlestickConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXCandlestickChartView(DEXView): 15 | chart_mode: Literal["candlestick"] = "candlestick" 16 | chart: DEXCandlestickConfig = Field(default_factory=DEXCandlestickConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/choropleth.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXChoroplethConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXChoroplethChartView(DEXView): 15 | chart_mode: Literal["choropleth"] = "choropleth" 16 | chart: DEXChoroplethConfig = Field(default_factory=DEXChoroplethConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/connected_scatter.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXConnectedScatterConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXConnectedScatterChartView(DEXView): 15 | chart_mode: Literal["connected_scatter"] = "connected_scatter" 16 | chart: DEXConnectedScatterConfig = Field(default_factory=DEXConnectedScatterConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/correlation_matrix.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXCorrelationMatrixConfig(DEXChartBase): 10 | pass 11 | 12 | 13 | class DEXCorrelationMatrixChartView(DEXView): 14 | chart_mode: Literal["correlation_matrix"] = "correlation_matrix" 15 | chart: DEXCorrelationMatrixConfig = Field(default_factory=DEXCorrelationMatrixConfig) 16 | -------------------------------------------------------------------------------- /src/dx/types/charts/cumulative.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXCumulativeConfig(DEXChartBase): 10 | pass 11 | 12 | 13 | class DEXCumulativeChartView(DEXView): 14 | chart_mode: Literal["cumulative"] = "cumulative" 15 | chart: DEXCumulativeConfig = Field(default_factory=DEXCumulativeConfig) 16 | -------------------------------------------------------------------------------- /src/dx/types/charts/dataprism.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXDataPrismChartConfig(DEXChartBase): 10 | class Config: 11 | fields = { 12 | "suggestion_fields": {"include": True}, 13 | } 14 | 15 | 16 | class DEXDataPrismChartView(DEXView): 17 | chart_mode: Literal["dataprism"] = "dataprism" 18 | chart: DEXDataPrismChartConfig = Field(default_factory=DEXDataPrismChartConfig) 19 | -------------------------------------------------------------------------------- /src/dx/types/charts/dendrogram.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXDendrogramConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXDendrogramChartView(DEXView): 15 | chart_mode: Literal["dendrogram"] = "dendrogram" 16 | chart: DEXDendrogramConfig = Field(default_factory=DEXDendrogramConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/dimension_matrix.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXDimensionMatrixConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXDimensionMatrixChartView(DEXView): 15 | chart_mode: Literal["dimension_matrix"] = "dimension_matrix" 16 | chart: DEXDimensionMatrixConfig = Field(default_factory=DEXDimensionMatrixConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/diverging_bar.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXDivergingBarConfig(DEXChartBase): 10 | pass 11 | 12 | 13 | class DEXDivergingBarChartView(DEXView): 14 | chart_mode: Literal["diverging_bar"] = "diverging_bar" 15 | chart: DEXDivergingBarConfig = Field(default_factory=DEXDivergingBarConfig) 16 | -------------------------------------------------------------------------------- /src/dx/types/charts/donut.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXDonutConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXDonutChartView(DEXView): 15 | chart_mode: Literal["donut"] = "donut" 16 | chart: DEXDonutConfig = Field(default_factory=DEXDonutConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/dotplot.py: -------------------------------------------------------------------------------- 1 | from typing import Literal, Union 2 | 3 | from pydantic import Field 4 | from typing_extensions import Annotated 5 | 6 | from dx.types.charts._base import DEXChartBase 7 | from dx.types.dex_metadata import DEXView 8 | 9 | 10 | class DEXDotPlotConfig(DEXChartBase): 11 | bar_projection: Literal["horizontal"] = Field(alias="barProjection", default="horizontal") 12 | 13 | 14 | class DEXRadarPlotConfig(DEXChartBase): 15 | bar_projection: Literal["radial"] = Field(alias="barProjection", default="radial") 16 | 17 | 18 | DEXGenericDotPlotConfigs = Annotated[ 19 | Union[ 20 | DEXDotPlotConfig, 21 | DEXRadarPlotConfig, 22 | ], 23 | Field(discriminator="bar_projection"), 24 | ] 25 | 26 | 27 | class DEXGenericDotPlotChartView(DEXView): 28 | chart_mode: Literal["dotplot"] = "dotplot" 29 | chart: DEXGenericDotPlotConfigs 30 | 31 | 32 | class DEXDotPlotChartView(DEXGenericDotPlotChartView): 33 | chart: DEXDotPlotConfig = Field(default_factory=DEXDotPlotConfig) 34 | 35 | 36 | class DEXRadarPlotChartView(DEXGenericDotPlotChartView): 37 | chart: DEXRadarPlotConfig = Field(default_factory=DEXRadarPlotConfig) 38 | -------------------------------------------------------------------------------- /src/dx/types/charts/flow_diagram.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXFlowDiagramConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXFlowDiagramChartView(DEXView): 15 | chart_mode: Literal["flow_diagram"] = "flow_diagram" 16 | chart: DEXFlowDiagramConfig = Field(default_factory=DEXFlowDiagramConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/force_directed_network.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts import options 6 | from dx.types.charts._base import DEXChartBase 7 | from dx.types.dex_metadata import DEXView 8 | 9 | 10 | class DEXForceDirectedNetworkConfig(DEXChartBase): 11 | # see dx.types.charts._base.DEXChartBase for available fields and configs 12 | network_type: options.DEXNetworkType = Field( 13 | alias="networkType", 14 | default=options.DEXNetworkType.force, 15 | ) 16 | pass 17 | 18 | 19 | class DEXForceDirectedNetworkChartView(DEXView): 20 | chart_mode: Literal["network"] = "network" 21 | chart: DEXForceDirectedNetworkConfig = Field(default_factory=DEXForceDirectedNetworkConfig) 22 | -------------------------------------------------------------------------------- /src/dx/types/charts/funnel.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXFunnelConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXFunnelChartView(DEXView): 15 | chart_mode: Literal["funnel"] = "funnel" 16 | chart: DEXFunnelConfig = Field(default_factory=DEXFunnelConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/funnel_chart.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXFunnelChartConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXFunnelChartChartView(DEXView): 15 | chart_mode: Literal["funnel_chart"] = "funnel_chart" 16 | chart: DEXFunnelChartConfig = Field(default_factory=DEXFunnelChartConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/funnel_sunburst.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXFunnelSunburstConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXFunnelSunburstChartView(DEXView): 15 | chart_mode: Literal["funnel_sunburst"] = "funnel_sunburst" 16 | chart: DEXFunnelSunburstConfig = Field(default_factory=DEXFunnelSunburstConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/funnel_tree.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXFunnelTreeConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXFunnelTreeChartView(DEXView): 15 | chart_mode: Literal["funnel_tree"] = "funnel_tree" 16 | chart: DEXFunnelTreeConfig = Field(default_factory=DEXFunnelTreeConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/grid.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXGridConfig(DEXChartBase): 10 | pass 11 | 12 | 13 | class DEXGridChartView(DEXView): 14 | chart_mode: Literal["grid"] = "grid" 15 | chart: DEXGridConfig = Field(default_factory=DEXGridConfig) 16 | -------------------------------------------------------------------------------- /src/dx/types/charts/hexbin.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXHexbinChartConfig(DEXChartBase): 10 | class Config: 11 | fields = { 12 | "metric1": {"include": True}, 13 | "metric2": {"include": True}, 14 | } 15 | 16 | 17 | class DEXHexbinChartView(DEXView): 18 | chart_mode: Literal["hexbin"] = "hexbin" 19 | chart: DEXHexbinChartConfig = Field(default_factory=DEXHexbinChartConfig) 20 | -------------------------------------------------------------------------------- /src/dx/types/charts/line.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXLineChartConfig(DEXChartBase): 10 | class Config: 11 | fields = { 12 | "bounding_type": {"include": True}, 13 | "combination_mode": {"include": True}, 14 | "line_smoothing": {"include": True}, 15 | "line_type": {"include": True}, 16 | "multi_axis_line": {"include": True}, 17 | "selected_metrics": {"include": True}, 18 | "split_lines_by": {"include": True}, 19 | "timeseries_sort": {"include": True}, 20 | "zero_baseline": {"include": True}, 21 | } 22 | 23 | 24 | class DEXLineChartView(DEXView): 25 | chart_mode: Literal["line"] = "line" 26 | chart: DEXLineChartConfig = Field(default_factory=DEXLineChartConfig) 27 | -------------------------------------------------------------------------------- /src/dx/types/charts/line_percent.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXLinePercentConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXLinePercentChartView(DEXView): 15 | chart_mode: Literal["line_percent"] = "line_percent" 16 | chart: DEXLinePercentConfig = Field(default_factory=DEXLinePercentConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/parcoords.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXParallelCoordinatesChartConfig(DEXChartBase): 10 | class Config: 11 | fields = {"include": {"selected_dimensions"}} 12 | 13 | 14 | class DEXParallelCoordinatesChartView(DEXView): 15 | chart_mode: Literal["parcoords"] = "parcoords" 16 | chart: DEXParallelCoordinatesChartConfig = Field( 17 | default_factory=DEXParallelCoordinatesChartConfig 18 | ) 19 | -------------------------------------------------------------------------------- /src/dx/types/charts/partition.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXPartitionConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXPartitionChartView(DEXView): 15 | chart_mode: Literal["partition"] = "partition" 16 | chart: DEXPartitionConfig = Field(default_factory=DEXPartitionConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/pie.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXPieChartConfig(DEXChartBase): 10 | class Config: 11 | fields = { 12 | "show_total": {"include": True}, 13 | "dim1": {"include": True}, # split by 14 | "metric1": {"include": True}, # slice size metric 15 | "pie_label_type": {"include": True}, 16 | "pie_label_contents": {"include": True}, 17 | } 18 | 19 | 20 | class DEXPieChartView(DEXView): 21 | chart_mode: Literal["pie"] = "pie" 22 | chart: DEXPieChartConfig = Field(default_factory=DEXPieChartConfig) 23 | -------------------------------------------------------------------------------- /src/dx/types/charts/sankey.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXSankeyConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXSankeyChartView(DEXView): 15 | chart_mode: Literal["sankey"] = "sankey" 16 | chart: DEXSankeyConfig = Field(default_factory=DEXSankeyConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/scatter.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXScatterChartConfig(DEXChartBase): 10 | class Config: 11 | fields = { 12 | "formula_display": {"include": True}, 13 | "marginal_graphics": {"include": True}, 14 | "metric1": {"include": True}, 15 | "metric2": {"include": True}, 16 | "scatterplot_size": {"include": True}, 17 | "selected_metrics": {"include": True}, 18 | "show_contours": {"include": True}, 19 | "splom_mode": {"include": True}, 20 | "trend_line": {"include": True}, 21 | } 22 | 23 | 24 | class DEXScatterChartView(DEXView): 25 | chart_mode: Literal["scatter"] = "scatter" 26 | chart: DEXScatterChartConfig = Field(default_factory=DEXScatterChartConfig) 27 | -------------------------------------------------------------------------------- /src/dx/types/charts/scatterplot_matrix.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXScatterPlotMatrixConfig(DEXChartBase): 10 | pass 11 | 12 | 13 | class DEXScatterPlotMatrixChartView(DEXView): 14 | chart_mode: Literal["scatterplot_matrix"] = "scatterplot_matrix" 15 | chart: DEXScatterPlotMatrixConfig = Field(default_factory=DEXScatterPlotMatrixConfig) 16 | -------------------------------------------------------------------------------- /src/dx/types/charts/stacked_area.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXStackedAreaConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXStackedAreaChartView(DEXView): 15 | chart_mode: Literal["stacked_area"] = "stacked_area" 16 | chart: DEXStackedAreaConfig = Field(default_factory=DEXStackedAreaConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/stacked_percent.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXStackedPercentConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXStackedPercentChartView(DEXView): 15 | chart_mode: Literal["stacked_percent"] = "stacked_percent" 16 | chart: DEXStackedPercentConfig = Field(default_factory=DEXStackedPercentConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/summary.py: -------------------------------------------------------------------------------- 1 | from typing import Literal, Union 2 | 3 | from pydantic import Field 4 | from typing_extensions import Annotated 5 | 6 | from dx.types.charts._base import DEXChartBase 7 | from dx.types.dex_metadata import DEXView 8 | 9 | 10 | class DEXBoxplotChartConfig(DEXChartBase): 11 | summary_type: Literal["boxplot"] = Field(alias="summaryType", default="boxplot") 12 | 13 | class Config: 14 | fields = { 15 | "dim1": {"include": True}, 16 | "metric1": {"include": True}, 17 | "sort_columns_by": {"include": True}, 18 | "summary_type": {"include": True}, 19 | "boxplot_outliers": {"include": True}, 20 | } 21 | 22 | 23 | class DEXHeatmapChartConfig(DEXChartBase): 24 | summary_type: Literal["heatmap"] = Field(alias="summaryType", default="heatmap") 25 | 26 | class Config: 27 | fields = { 28 | "dim1": {"include": True}, 29 | "metric1": {"include": True}, 30 | "sort_columns_by": {"include": True}, 31 | "summary_type": {"include": True}, 32 | "summary_bins": {"include": True}, 33 | } 34 | 35 | 36 | class DEXHistogramChartConfig(DEXChartBase): 37 | summary_type: Literal["histogram"] = Field(alias="summaryType", default="histogram") 38 | 39 | class Config: 40 | fields = { 41 | "dim1": {"include": True}, 42 | "metric1": {"include": True}, 43 | "sort_columns_by": {"include": True}, 44 | "summary_type": {"include": True}, 45 | "summary_bins": {"include": True}, 46 | } 47 | 48 | 49 | class DEXHorizonChartConfig(DEXChartBase): 50 | summary_type: Literal["horizon"] = Field(alias="summaryType", default="horizon") 51 | 52 | class Config: 53 | fields = { 54 | "dim1": {"include": True}, 55 | "metric1": {"include": True}, 56 | "sort_columns_by": {"include": True}, 57 | "summary_type": {"include": True}, 58 | "summary_bins": {"include": True}, 59 | } 60 | 61 | 62 | class DEXRidgelineChartConfig(DEXChartBase): 63 | summary_type: Literal["ridgeline"] = Field(alias="summaryType", default="ridgeline") 64 | 65 | class Config: 66 | fields = { 67 | "dim1": {"include": True}, 68 | "metric1": {"include": True}, 69 | "sort_columns_by": {"include": True}, 70 | "summary_type": {"include": True}, 71 | "summary_bins": {"include": True}, 72 | } 73 | 74 | 75 | class DEXViolinChartConfig(DEXChartBase): 76 | summary_type: Literal["violin"] = Field(alias="summaryType", default="violin") 77 | 78 | class Config: 79 | fields = { 80 | "dim1": {"include": True}, 81 | "metric1": {"include": True}, 82 | "sort_columns_by": {"include": True}, 83 | "summary_type": {"include": True}, 84 | "summary_bins": {"include": True}, 85 | "violin_iqr": {"include": True}, 86 | } 87 | 88 | 89 | DEXSummaryChartConfig = Annotated[ 90 | Union[ 91 | DEXBoxplotChartConfig, 92 | DEXHeatmapChartConfig, 93 | DEXHistogramChartConfig, 94 | DEXHorizonChartConfig, 95 | DEXRidgelineChartConfig, 96 | DEXViolinChartConfig, 97 | ], 98 | Field(discriminator="summary_type"), 99 | ] 100 | 101 | 102 | class DEXSummaryChartView(DEXView): 103 | chart_mode: Literal["summary"] = "summary" 104 | chart: DEXSummaryChartConfig 105 | 106 | 107 | class DEXBoxplotChartView(DEXSummaryChartView): 108 | chart: DEXBoxplotChartConfig 109 | 110 | 111 | class DEXHeatmapChartView(DEXSummaryChartView): 112 | chart: DEXHeatmapChartConfig 113 | 114 | 115 | class DEXHistogramChartView(DEXSummaryChartView): 116 | chart: DEXHistogramChartConfig 117 | 118 | 119 | class DEXHorizonChartView(DEXSummaryChartView): 120 | chart: DEXHorizonChartConfig 121 | 122 | 123 | class DEXRidgelineChartView(DEXSummaryChartView): 124 | chart: DEXRidgelineChartConfig 125 | 126 | 127 | class DEXViolinChartView(DEXSummaryChartView): 128 | chart: DEXViolinChartConfig 129 | -------------------------------------------------------------------------------- /src/dx/types/charts/sunburst.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXSunburstConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXSunburstChartView(DEXView): 15 | chart_mode: Literal["sunburst"] = "sunburst" 16 | chart: DEXSunburstConfig = Field(default_factory=DEXSunburstConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/tilemap.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXTilemapChartConfig(DEXChartBase): 10 | class Config: 11 | fields = { 12 | "map_mode": {"include": True}, # "tile" 13 | "layer_settings": {"include": True}, 14 | } 15 | 16 | 17 | class DEXTilemapChartView(DEXView): 18 | chart_mode: Literal["tilemap"] = "tilemap" 19 | chart: DEXTilemapChartConfig = Field(default_factory=DEXTilemapChartConfig) 20 | -------------------------------------------------------------------------------- /src/dx/types/charts/treemap.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXTreemapConfig(DEXChartBase): 10 | # see dx.types.charts._base.DEXChartBase for available fields and configs 11 | pass 12 | 13 | 14 | class DEXTreemapChartView(DEXView): 15 | chart_mode: Literal["treemap"] = "treemap" 16 | chart: DEXTreemapConfig = Field(default_factory=DEXTreemapConfig) 17 | -------------------------------------------------------------------------------- /src/dx/types/charts/wordcloud.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from dx.types.charts._base import DEXChartBase 6 | from dx.types.dex_metadata import DEXView 7 | 8 | 9 | class DEXWordcloudChartConfig(DEXChartBase): 10 | class Config: 11 | fields = { 12 | "text_data_format": {"include": True}, 13 | "token_metric": {"include": True}, 14 | "word_color": {"include": True}, 15 | "word_data": {"include": True}, 16 | "word_rotate": {"include": True}, 17 | } 18 | 19 | 20 | class DEXWordcloudChartView(DEXView): 21 | chart_mode: Literal["wordcloud"] = "wordcloud" 22 | chart: DEXWordcloudChartConfig = Field(default_factory=DEXWordcloudChartConfig) 23 | -------------------------------------------------------------------------------- /src/dx/types/main.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | import structlog 4 | 5 | logger = structlog.get_logger(__name__) 6 | 7 | 8 | # --- Enums --- 9 | class BaseEnum(enum.Enum): 10 | def __str__(self): 11 | return str(self.value) 12 | 13 | def __eq__(self, other): 14 | return str(other) == self.value 15 | 16 | 17 | class DXDisplayMode(BaseEnum): 18 | enhanced = "enhanced" # GRID display 19 | simple = "simple" # classic simpleTable/DEX display 20 | plain = "plain" # basic/vanilla python/pandas display 21 | 22 | 23 | class DXSamplingMethod(BaseEnum): 24 | first = "first" # df.head(num_rows) 25 | inner = "inner" # middle rows 26 | last = "last" # df.tail(num_rows) 27 | outer = "outer" # df.head(num_rows/2) & df.tail(num_rows/2) 28 | random = "random" # df.sample(num_rows) 29 | 30 | 31 | class DEXMediaType(BaseEnum): 32 | dataresource = "application/vnd.dataresource+json" 33 | dex = "application/vnd.dex.v1+json" 34 | -------------------------------------------------------------------------------- /src/dx/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSeal/dx/957e8050de67588bb31fa64155871209abcdb69a/src/dx/utils/__init__.py -------------------------------------------------------------------------------- /tests/test_dataresource.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | import pytest 4 | 5 | from dx.datatypes.main import quick_random_dataframe 6 | from dx.formatters.main import format_output, generate_body 7 | from dx.formatters.simple import get_dataresource_settings 8 | from dx.settings import settings_context 9 | 10 | dataresource_settings = get_dataresource_settings() 11 | 12 | 13 | def test_simple_data_structure(sample_dataframe): 14 | """ 15 | The transformed data needs to represent a list of lists, 16 | each associated with a column in the dataframe, 17 | including one for the dataframe's index. 18 | """ 19 | display_id = str(uuid.uuid4()) 20 | with settings_context(display_mode="simple"): 21 | payload = generate_body(sample_dataframe, display_id) 22 | data = payload["data"] 23 | assert isinstance(data, list) 24 | assert len(data) == 3 25 | assert isinstance(data[0], dict) 26 | 27 | 28 | def test_simple_data_order(sample_dataframe): 29 | """ 30 | Ensure the payload contains lists as row values, 31 | and not column values. 32 | """ 33 | display_id = str(uuid.uuid4()) 34 | with settings_context(display_mode="simple"): 35 | payload = generate_body(sample_dataframe, display_id) 36 | data = payload["data"] 37 | assert data[0] == {"col_1": "a", "col_2": "b", "col_3": "c", "index": 0} 38 | assert data[1] == {"col_1": "a", "col_2": "b", "col_3": "c", "index": 1} 39 | assert data[2] == {"col_1": "a", "col_2": "b", "col_3": "c", "index": 2} 40 | 41 | 42 | def test_fields_match_data_width(sample_dataframe): 43 | """ 44 | The number of fields in the schema needs to match 45 | the number of dictionary keys per item in the data list. 46 | """ 47 | display_id = str(uuid.uuid4()) 48 | with settings_context(display_mode="simple"): 49 | payload = generate_body(sample_dataframe, display_id) 50 | data = payload["data"] 51 | fields = payload["schema"]["fields"] 52 | assert len(data[0]) == len(fields) 53 | 54 | 55 | @pytest.mark.parametrize("enabled", [True, False]) 56 | def test_datalink_toggle(enabled: bool): 57 | """ 58 | A dataframe with mixed types should be able to make it through 59 | the entire "simple" format process with datalink enabled. 60 | """ 61 | df = quick_random_dataframe() 62 | with settings_context(enable_datalink=enabled, display_mode="simple"): 63 | try: 64 | format_output(df) 65 | except Exception as e: 66 | assert False, f"failed with {e}" 67 | -------------------------------------------------------------------------------- /tests/test_datatype_handling.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from dx.datatypes.main import SORTED_DX_DATATYPES, random_dataframe 4 | from dx.utils.formatting import clean_series_values 5 | 6 | 7 | @pytest.mark.benchmark 8 | @pytest.mark.parametrize("dtype", SORTED_DX_DATATYPES) 9 | def test_benchmark_column_cleaning( 10 | benchmark, 11 | dtype: str, 12 | num_rows: int = 50_000, 13 | ): 14 | params = {dt: False for dt in SORTED_DX_DATATYPES} 15 | params[dtype] = True 16 | df = random_dataframe(num_rows, **params) 17 | for col in df.columns: 18 | benchmark(clean_series_values, df[col]) 19 | 20 | # do something else here? 21 | assert 1 == 1 22 | -------------------------------------------------------------------------------- /tests/test_dx.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | import pytest 4 | 5 | from dx.datatypes.main import quick_random_dataframe 6 | from dx.formatters.enhanced import get_dx_settings 7 | from dx.formatters.main import format_output, generate_body 8 | from dx.settings import settings_context 9 | 10 | dx_settings = get_dx_settings() 11 | 12 | 13 | def test_data_structure(sample_dataframe): 14 | """ 15 | The transformed data needs to represent a list of lists, 16 | each associated with a column in the dataframe, 17 | including one for the dataframe's index. 18 | """ 19 | display_id = str(uuid.uuid4()) 20 | with settings_context(display_mode="enhanced"): 21 | payload = generate_body(sample_dataframe, display_id) 22 | data = payload["data"] 23 | assert isinstance(data, list) 24 | assert len(data) == 4 25 | assert isinstance(data[0], list) 26 | 27 | 28 | def test_data_list_order(sample_dataframe): 29 | """ 30 | Ensure the payload contains lists as column values, 31 | and not row values. 32 | """ 33 | display_id = str(uuid.uuid4()) 34 | with settings_context(display_mode="enhanced"): 35 | payload = generate_body(sample_dataframe, display_id) 36 | data = payload["data"] 37 | assert data[0] == [0, 1, 2] # index values 38 | assert data[1] == list("aaa") # "col_1" values 39 | assert data[2] == list("bbb") # "col_2" values 40 | assert data[3] == list("ccc") # "col_3" values 41 | 42 | 43 | def test_fields_match_data_length(sample_dataframe): 44 | """ 45 | The number of fields in the schema needs to match 46 | the number of lists in the data list. 47 | """ 48 | display_id = str(uuid.uuid4()) 49 | with settings_context(display_mode="enhanced"): 50 | payload = generate_body(sample_dataframe, display_id) 51 | data = payload["data"] 52 | fields = payload["schema"]["fields"] 53 | assert len(data) == len(fields) 54 | 55 | 56 | @pytest.mark.parametrize("enabled", [True, False]) 57 | def test_datalink_toggle(enabled: bool): 58 | """ 59 | A dataframe with mixed types should be able to make it through 60 | the entire "enhanced" format process with datalink enabled. 61 | """ 62 | df = quick_random_dataframe() 63 | with settings_context(enable_datalink=enabled, display_mode="enhanced"): 64 | try: 65 | format_output(df) 66 | except Exception as e: 67 | assert False, f"failed with {e}" 68 | -------------------------------------------------------------------------------- /tests/test_registering.py: -------------------------------------------------------------------------------- 1 | from IPython.terminal.interactiveshell import TerminalInteractiveShell 2 | 3 | from dx.formatters.enhanced import get_dx_settings, register 4 | from dx.formatters.main import DEFAULT_IPYTHON_DISPLAY_FORMATTER, DXDisplayFormatter 5 | from dx.formatters.plain import get_pandas_settings, reset 6 | from dx.formatters.simple import deregister, get_dataresource_settings 7 | from dx.settings import get_settings, settings_context 8 | 9 | dataresource_settings = get_dataresource_settings() 10 | dx_settings = get_dx_settings() 11 | pandas_settings = get_pandas_settings() 12 | settings = get_settings() 13 | 14 | 15 | def test_register_ipython_display_formatter(get_ipython: TerminalInteractiveShell): 16 | """ 17 | Test that the display formatter for an IPython shell is 18 | successfully registered as a DXDisplayFormatter and that 19 | global settings have been properly updated. 20 | """ 21 | with settings_context(ipython_shell=get_ipython, display_mode="plain"): 22 | register(ipython_shell=get_ipython) 23 | 24 | assert isinstance(get_ipython.display_formatter, DXDisplayFormatter) 25 | 26 | settings_to_apply = { 27 | "DISPLAY_MAX_COLUMNS", 28 | "DISPLAY_MAX_ROWS", 29 | "MEDIA_TYPE", 30 | "FLATTEN_INDEX_VALUES", 31 | "FLATTEN_COLUMN_VALUES", 32 | "STRINGIFY_INDEX_VALUES", 33 | "STRINGIFY_COLUMN_VALUES", 34 | } 35 | for setting in settings_to_apply: 36 | val = getattr(dx_settings, f"DX_{setting}", None) 37 | assert getattr(settings, setting) == val 38 | 39 | 40 | def test_deregister_ipython_display_formatter(get_ipython: TerminalInteractiveShell): 41 | """ 42 | Test that the display formatter for an IPython shell is 43 | successfully registered as a DXDisplayFormatter 44 | and that global settings have been properly updated. 45 | """ 46 | with settings_context(ipython_shell=get_ipython, display_mode="plain"): 47 | deregister(ipython_shell=get_ipython) 48 | 49 | assert isinstance(get_ipython.display_formatter, DXDisplayFormatter) 50 | 51 | settings_to_apply = { 52 | "DISPLAY_MAX_COLUMNS", 53 | "DISPLAY_MAX_ROWS", 54 | "MEDIA_TYPE", 55 | "FLATTEN_INDEX_VALUES", 56 | "FLATTEN_COLUMN_VALUES", 57 | "STRINGIFY_INDEX_VALUES", 58 | "STRINGIFY_COLUMN_VALUES", 59 | } 60 | for setting in settings_to_apply: 61 | val = getattr(dataresource_settings, f"DATARESOURCE_{setting}", None) 62 | assert getattr(settings, setting) == val 63 | 64 | 65 | def test_reset_ipython_display_formatter(get_ipython: TerminalInteractiveShell): 66 | """ 67 | Test that the display formatter reverts to the default 68 | `IPython.core.formatters.DisplayFormatter` after resetting 69 | and that global settings have been properly updated. 70 | """ 71 | with settings_context(ipython_shell=get_ipython, display_mode="simple"): 72 | reset(ipython_shell=get_ipython) 73 | 74 | assert get_ipython.display_formatter == DEFAULT_IPYTHON_DISPLAY_FORMATTER 75 | assert not isinstance(get_ipython.display_formatter, DXDisplayFormatter) 76 | 77 | 78 | def test_default_media_types_remain(get_ipython: TerminalInteractiveShell): 79 | """ 80 | Test that setting the dispay mode to "simple" or "enhanced" does not remove 81 | any existing formatters for unrelated media types: 82 | application/javascript 83 | application/json 84 | application/pdf 85 | text/html 86 | text/latex 87 | text/markdown 88 | text/plain 89 | image/jpeg 90 | image/png 91 | image/svg+xml 92 | """ 93 | 94 | default_media_types = set(DEFAULT_IPYTHON_DISPLAY_FORMATTER.formatters.keys()) 95 | assert default_media_types != set() 96 | 97 | with settings_context(ipython_shell=get_ipython, display_mode="plain"): 98 | deregister(ipython_shell=get_ipython) 99 | dataresource_formatter_keys = set(get_ipython.display_formatter.formatters.keys()) 100 | assert dataresource_formatter_keys & default_media_types == default_media_types 101 | 102 | with settings_context(ipython_shell=get_ipython, display_mode="plain"): 103 | register(ipython_shell=get_ipython) 104 | dx_formatter_keys = set(get_ipython.display_formatter.formatters.keys()) 105 | assert dx_formatter_keys & default_media_types == default_media_types 106 | -------------------------------------------------------------------------------- /tests/test_renderable_types.py: -------------------------------------------------------------------------------- 1 | import dask.dataframe as dd 2 | import geopandas as gpd 3 | import modin.pandas as mpd 4 | import pandas as pd 5 | import polars as pl 6 | import pytest 7 | import vaex 8 | from IPython.terminal.interactiveshell import TerminalInteractiveShell 9 | 10 | from dx.formatters.main import handle_format 11 | from dx.settings import get_settings, settings_context 12 | from dx.utils.formatting import to_dataframe 13 | from dx.utils.tracking import DXDF_CACHE 14 | 15 | settings = get_settings() 16 | 17 | 18 | def sample_data(): 19 | return { 20 | "a": [1, 2, None], 21 | "b": [4, None, 6], 22 | } 23 | 24 | 25 | def sample_dask_dataframe(): 26 | return dd.from_pandas(pd.DataFrame(sample_data()), npartitions=2) 27 | 28 | 29 | def sample_geopandas_geodataframe(): 30 | data = sample_data() 31 | return gpd.GeoDataFrame( 32 | data, 33 | geometry=gpd.points_from_xy(data["a"], data["b"]), 34 | ) 35 | 36 | 37 | def sample_modin_dataframe(): 38 | return mpd.DataFrame(sample_data()) 39 | 40 | 41 | def sample_polars_dataframe(): 42 | return pl.DataFrame(sample_data()) 43 | 44 | 45 | def sample_vaex_dataframe(): 46 | data = sample_data() 47 | return vaex.from_arrays(a=data["a"], b=data["b"]) 48 | 49 | 50 | DATAFRAME_TYPES = ["dask", "geopandas", "modin", "polars", "vaex"] 51 | 52 | 53 | class TestRenderableTypes: 54 | @pytest.mark.parametrize("renderable_type", DATAFRAME_TYPES) 55 | @pytest.mark.parametrize("datalink_enabled", [True, False]) 56 | @pytest.mark.parametrize("display_mode", ["simple", "enhanced"]) 57 | def test_non_pandas_dataframes( 58 | self, 59 | renderable_type: str, 60 | display_mode: str, 61 | datalink_enabled: bool, 62 | get_ipython: TerminalInteractiveShell, 63 | ): 64 | """ 65 | Test formatting a supported renderable data type that isn't a pandas DataFrame, 66 | to include the additional processing and tracking handled for datalink. 67 | 68 | Additionally, if datalink is enabled, ensure that the display ID was 69 | generated within the DXDataFrame and stored in the DXDF_CACHE. 70 | """ 71 | if renderable_type == "dask": 72 | obj = sample_dask_dataframe() 73 | elif renderable_type == "geopandas": 74 | obj = sample_geopandas_geodataframe() 75 | elif renderable_type == "modin": 76 | obj = sample_modin_dataframe() 77 | elif renderable_type == "polars": 78 | obj = sample_polars_dataframe() 79 | elif renderable_type == "vaex": 80 | obj = sample_vaex_dataframe() 81 | 82 | df = to_dataframe(obj) 83 | assert isinstance(df, pd.DataFrame) 84 | get_ipython.user_ns["test_df"] = df 85 | 86 | # check structure after converting to dataframe 87 | # to make sure we didn't lose any rows/columns/etc 88 | assert len(df.columns) == len(obj.columns) 89 | 90 | if renderable_type in ["dask"]: 91 | assert len(df) == len(obj.compute()) 92 | else: 93 | assert len(df) == len(obj) 94 | 95 | if hasattr(obj, "index"): 96 | assert list(df.index) == list(obj.index) 97 | 98 | try: 99 | with settings_context(enable_datalink=datalink_enabled, display_mode=display_mode): 100 | _, metadata = handle_format(df, ipython_shell=get_ipython) 101 | if datalink_enabled: 102 | display_id = metadata[settings.MEDIA_TYPE]["display_id"] 103 | assert display_id in DXDF_CACHE 104 | assert metadata[settings.MEDIA_TYPE]["datalink"]["variable_name"] == "test_df" 105 | except Exception as e: 106 | assert False, f"{e}" 107 | -------------------------------------------------------------------------------- /tests/test_sampling.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import pandas as pd 4 | 5 | from dx.sampling import sample_if_too_big 6 | from dx.settings import get_settings, settings_context 7 | 8 | settings = get_settings() 9 | 10 | 11 | def test_small_dataframe_is_not_sampled(sample_dataframe: pd.DataFrame): 12 | """ 13 | Test that a small dataframe is not sampled. 14 | """ 15 | original_size_bytes = sys.getsizeof(sample_dataframe) 16 | sampled_df = sample_if_too_big(sample_dataframe) 17 | sampled_size_bytes = sys.getsizeof(sampled_df) 18 | assert sampled_size_bytes <= settings.MAX_RENDER_SIZE_BYTES 19 | assert sampled_size_bytes == original_size_bytes 20 | 21 | 22 | def test_large_dataframe_is_sampled(sample_large_dataframe: pd.DataFrame): 23 | """ 24 | Test that a large dataframe is sampled to below the size of 25 | MAX_RENDER_SIZE_BYTES. 26 | """ 27 | with settings_context( 28 | MAX_RENDER_SIZE_BYTES=1024 * 1024, 29 | DISPLAY_MAX_ROWS=100, 30 | DISPLAY_MAX_COLUMNS=100, 31 | ): 32 | original_size_bytes = sys.getsizeof(sample_large_dataframe) 33 | sampled_df = sample_if_too_big(sample_large_dataframe) 34 | sampled_size_bytes = sys.getsizeof(sampled_df) 35 | assert sampled_size_bytes <= settings.MAX_RENDER_SIZE_BYTES 36 | assert sampled_size_bytes < original_size_bytes 37 | 38 | 39 | def test_sampled_dataframe_keeps_dtypes(sample_large_dataframe: pd.DataFrame): 40 | """ 41 | Test that a sampled dataframe doesn't alter column datatypes. 42 | """ 43 | with settings_context( 44 | MAX_RENDER_SIZE_BYTES=1024 * 1024, 45 | DISPLAY_MAX_ROWS=100, 46 | DISPLAY_MAX_COLUMNS=100, 47 | ): 48 | orig_dtypes = sample_large_dataframe.dtypes 49 | sampled_df = sample_if_too_big(sample_large_dataframe) 50 | assert (sampled_df.dtypes == orig_dtypes).all() 51 | 52 | 53 | def test_wide_dataframe_is_narrowed(sample_wide_dataframe: pd.DataFrame): 54 | """ 55 | Test that a wide dataframe is narrowed to below the size of 56 | the display mode's MAX_COLUMNS setting. 57 | """ 58 | orig_width = len(sample_wide_dataframe.columns) 59 | narrow_df = sample_if_too_big(sample_wide_dataframe) 60 | narrow_width = len(narrow_df.columns) 61 | assert narrow_width < orig_width, f"{narrow_width=}" 62 | assert narrow_width <= settings.DISPLAY_MAX_COLUMNS 63 | 64 | 65 | def test_long_dataframe_is_shortened(sample_long_dataframe: pd.DataFrame): 66 | """ 67 | Test that a long dataframe is shortened to below the size of 68 | the display mode's MAX_ROWS setting. 69 | """ 70 | orig_length = len(sample_long_dataframe) 71 | short_df = sample_if_too_big(sample_long_dataframe) 72 | short_length = len(short_df) 73 | assert short_length < orig_length, f"{short_length=}" 74 | assert short_length <= settings.DISPLAY_MAX_ROWS 75 | 76 | 77 | def test_long_wide_dataframe_is_reduced_from_both_dimensions( 78 | sample_long_wide_dataframe: pd.DataFrame, 79 | ): 80 | """ 81 | Test that a long wide dataframe is reduced from both dimensions 82 | to below the size of the display mode's MAX_COLUMNS and MAX_ROWS settings. 83 | """ 84 | orig_width = len(sample_long_wide_dataframe.columns) 85 | orig_length = len(sample_long_wide_dataframe) 86 | reduced_df = sample_if_too_big(sample_long_wide_dataframe) 87 | reduced_width = len(reduced_df.columns) 88 | reduced_length = len(reduced_df) 89 | assert reduced_width < orig_width 90 | assert reduced_width <= settings.DISPLAY_MAX_COLUMNS 91 | assert reduced_length < orig_length 92 | assert reduced_length <= settings.DISPLAY_MAX_ROWS 93 | 94 | 95 | def test_big_value_dataframe_is_character_limited(): 96 | """ 97 | Test that a dataframe with large string values will be truncated 98 | based on the display mode's MAX_STRING_LENGTH setting. 99 | """ 100 | df = pd.DataFrame( 101 | { 102 | "foo": [ 103 | "a" * 1, 104 | "b" * 10, 105 | "c" * 100, 106 | "d" * 1000, 107 | "e" * 10000, 108 | "f" * 100000, 109 | ] 110 | } 111 | ) 112 | orig_column = df["foo"].copy() 113 | sampled_df = sample_if_too_big(df) 114 | sampled_df["foo_length"] = sampled_df["foo"].apply(len) 115 | # if settings.MAX_STRING_LENGTH is set to 1000, then the rows with greater 116 | # than 1000 characters will be truncated to up to 1000 characters 117 | assert (sampled_df["foo_length"] <= settings.MAX_STRING_LENGTH).all() 118 | assert not sampled_df["foo"].equals(orig_column) 119 | -------------------------------------------------------------------------------- /tests/test_settings.py: -------------------------------------------------------------------------------- 1 | from IPython.terminal.interactiveshell import TerminalInteractiveShell 2 | 3 | from dx.settings import add_renderable_type, get_settings, set_display_mode, settings_context 4 | from dx.types.main import DXDisplayMode 5 | 6 | settings = get_settings() 7 | 8 | 9 | def test_set_display_mode(get_ipython: TerminalInteractiveShell): 10 | """ 11 | Make sure calling dx.set_display_mode() properly configures the 12 | IPython display formatter and updates settings. 13 | """ 14 | set_display_mode("plain", ipython_shell=get_ipython) 15 | assert settings.DISPLAY_MODE == DXDisplayMode.plain 16 | 17 | set_display_mode("simple", ipython_shell=get_ipython) 18 | assert settings.DISPLAY_MODE == DXDisplayMode.simple 19 | 20 | set_display_mode("enhanced", ipython_shell=get_ipython) 21 | assert settings.DISPLAY_MODE == DXDisplayMode.enhanced 22 | 23 | 24 | def test_settings_context_preserves_global_setting(get_ipython: TerminalInteractiveShell): 25 | """ 26 | Test that using the settings_context() context manager 27 | does not permanently change a global setting. 28 | """ 29 | set_display_mode("simple", ipython_shell=get_ipython) 30 | max_rows = settings.DISPLAY_MAX_ROWS 31 | 32 | with settings_context(display_max_rows=1, ipython_shell=get_ipython): 33 | assert settings.DISPLAY_MAX_ROWS == 1 34 | 35 | assert settings.DISPLAY_MAX_ROWS != 1 36 | assert settings.DISPLAY_MAX_ROWS == max_rows, f"{settings=}" 37 | 38 | 39 | def test_settings_context_preserves_global_display_mode(get_ipython: TerminalInteractiveShell): 40 | """ 41 | Test that using the settings_context() context manager 42 | does not permanently change the display mode. 43 | """ 44 | set_display_mode("simple", ipython_shell=get_ipython) 45 | 46 | with settings_context(display_mode="enhanced", ipython_shell=get_ipython): 47 | assert settings.DISPLAY_MODE == DXDisplayMode.enhanced, f"{settings=}" 48 | 49 | assert settings.DISPLAY_MODE == DXDisplayMode.simple, f"{settings=}" 50 | 51 | 52 | def test_add_renderables(): 53 | class FakeRenderable: 54 | pass 55 | 56 | add_renderable_type(FakeRenderable) 57 | assert FakeRenderable in settings.get_renderable_types().keys() 58 | -------------------------------------------------------------------------------- /tests/test_tracking.py: -------------------------------------------------------------------------------- 1 | import duckdb 2 | import pandas as pd 3 | from IPython.terminal.interactiveshell import TerminalInteractiveShell 4 | 5 | from dx.formatters.main import handle_format 6 | from dx.settings import settings_context 7 | from dx.utils.formatting import normalize_index_and_columns 8 | from dx.utils.tracking import DXDataFrame, generate_df_hash 9 | 10 | 11 | def test_dxdataframe( 12 | sample_random_dataframe: pd.DataFrame, 13 | get_ipython: TerminalInteractiveShell, 14 | ): 15 | """ 16 | Ensure we can create a DXDataFrame from a pandas DataFrame. 17 | """ 18 | try: 19 | DXDataFrame( 20 | df=sample_random_dataframe, 21 | ipython_shell=get_ipython, 22 | ) 23 | except Exception as e: 24 | assert False, f"{e}" 25 | 26 | 27 | class TestVariableTracking: 28 | def test_dxdataframe_finds_variable_name( 29 | self, 30 | sample_random_dataframe: pd.DataFrame, 31 | get_ipython: TerminalInteractiveShell, 32 | ): 33 | """ 34 | Test that the DXDataFrame finds the correctly-assigned 35 | pandas DataFrame variable name. 36 | """ 37 | get_ipython.user_ns["test_df"] = sample_random_dataframe 38 | 39 | dxdf = DXDataFrame( 40 | df=sample_random_dataframe, 41 | ipython_shell=get_ipython, 42 | ) 43 | assert dxdf.variable_name == "test_df" 44 | 45 | def test_dxdataframe_creates_variable_name( 46 | self, 47 | sample_random_dataframe: pd.DataFrame, 48 | get_ipython: TerminalInteractiveShell, 49 | ): 50 | """ 51 | Test that the DXDataFrame creates a new temporary variable 52 | for dataframes that haven't been assigned within the user's 53 | namespace. 54 | """ 55 | dxdf = DXDataFrame( 56 | df=sample_random_dataframe, 57 | ipython_shell=get_ipython, 58 | ) 59 | assert dxdf.variable_name.startswith("unk_dataframe") 60 | 61 | 62 | def test_dxdataframe_creates_hash( 63 | sample_random_dataframe: pd.DataFrame, 64 | get_ipython: TerminalInteractiveShell, 65 | ): 66 | """ 67 | Test that the DXDataFrame creates a unique hash for the 68 | dataframe after cleaning the index and columns. 69 | """ 70 | dxdf = DXDataFrame( 71 | df=sample_random_dataframe, 72 | ipython_shell=get_ipython, 73 | ) 74 | clean_sample_dataframe = normalize_index_and_columns(sample_random_dataframe) 75 | assert dxdf.hash == generate_df_hash(clean_sample_dataframe) 76 | 77 | 78 | def test_dxdataframe_metadata( 79 | sample_dxdataframe: DXDataFrame, 80 | sample_cleaned_random_dataframe: pd.DataFrame, 81 | ): 82 | """ 83 | Test that the DXDataFrame creates metadata for the frontend 84 | including the appropriate dataframe information and datalink keys. 85 | 86 | (Similar to ./test_formatting.py::TestMetadataStructure) 87 | """ 88 | metadata = sample_dxdataframe.metadata 89 | assert "datalink" in metadata and "display_id" in metadata 90 | assert metadata["display_id"] == sample_dxdataframe.display_id 91 | assert metadata["datalink"]["display_id"] == sample_dxdataframe.display_id 92 | 93 | 94 | def test_store_in_db( 95 | mocker, 96 | get_ipython: TerminalInteractiveShell, 97 | sample_random_dataframe: pd.DataFrame, 98 | sample_db_connection: duckdb.DuckDBPyConnection, 99 | ): 100 | """ 101 | Ensure dataframes are stored as tables using the kernel's 102 | local database connection. 103 | """ 104 | mocker.patch("dx.formatters.main.db_connection", sample_db_connection) 105 | 106 | get_ipython.user_ns["test_df"] = sample_random_dataframe 107 | 108 | with settings_context(enable_datalink=True): 109 | handle_format( 110 | sample_random_dataframe, 111 | ipython_shell=get_ipython, 112 | ) 113 | 114 | resp = sample_db_connection.execute("SELECT COUNT(*) FROM test_df").fetchone() 115 | assert resp[0] == len(sample_random_dataframe) 116 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from dx.settings import get_settings, settings_context 4 | from dx.utils.formatting import is_default_index, normalize_index_and_columns 5 | 6 | settings = get_settings() 7 | 8 | 9 | def test_default_index_returns_true(sample_dataframe: pd.DataFrame): 10 | index = sample_dataframe.index 11 | assert is_default_index(index) 12 | 13 | 14 | def test_unsorted_default_index_returns_true(sample_dataframe: pd.DataFrame): 15 | shuffled_sample_dataframe = sample_dataframe.sample(len(sample_dataframe)) 16 | index = shuffled_sample_dataframe.index 17 | assert is_default_index(index) 18 | 19 | 20 | def test_custom_index_returns_false(sample_dataframe: pd.DataFrame): 21 | sample_dataframe.set_index("col_1", inplace=True) 22 | index = sample_dataframe.index 23 | assert not is_default_index(index) 24 | 25 | 26 | def test_multiindex_returns_false(sample_dataframe: pd.DataFrame): 27 | sample_dataframe.set_index(["col_1", "col_2"], inplace=True) 28 | index = sample_dataframe.index 29 | assert not is_default_index(index) 30 | 31 | 32 | def test_default_index_persists(sample_dataframe: pd.DataFrame): 33 | """ 34 | Default indexes should not be reset. 35 | """ 36 | with settings_context(STRINGIFY_INDEX_VALUES=False): 37 | df = normalize_index_and_columns(sample_dataframe.copy()) 38 | assert list(df.index) == list(sample_dataframe.index) 39 | 40 | 41 | def test_custom_index_resets(sample_dataframe: pd.DataFrame): 42 | """ 43 | Custom indexes should reset to ensure the `index` is passed 44 | with row value numbers to the frontend, from 0 to the length of the dataframe. 45 | """ 46 | with settings_context(RESET_INDEX_VALUES=True): 47 | sample_dataframe.set_index(["col_1", "col_2"], inplace=True) 48 | assert isinstance(sample_dataframe.index, pd.MultiIndex) 49 | df = normalize_index_and_columns(sample_dataframe.copy()) 50 | assert not df.index.equals(sample_dataframe.index) 51 | assert not isinstance(df.index, pd.MultiIndex) 52 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = testenv, py39 3 | isolated_build = true 4 | 5 | [testenv] 6 | basepython = python3.9 7 | command = pytest -vv . 8 | 9 | [testenv:py39] 10 | basepython = python3.9 --------------------------------------------------------------------------------