├── .eslintignore
├── .eslintrc.js
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── pull_request_template.md
├── screenshots
│ ├── Autoprofiler_social_tag.png
│ ├── demo.gif
│ └── profiler_sc.png
└── workflows
│ ├── build.yml
│ └── deploy_juplite.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .stylelintrc
├── .vscode
└── settings.json
├── CONTRIBUTING.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── RELEASE.md
├── TROUBLESHOOTING.md
├── digautoprofiler
├── __init__.py
├── _version.py
├── profile_lib.py
└── utils.py
├── environment.yml
├── examples
├── SF Housing Demo.ipynb
└── df_housing_sample.csv
├── install.json
├── jupyterlite-requirements.txt
├── package-lock.json
├── package.json
├── postcss.config.cjs
├── pyproject.toml
├── requirements.txt
├── setup.py
├── src
├── ProfilePanel.ts
├── common
│ └── exchangeInterfaces.ts
├── components
│ ├── ColumnEntry.svelte
│ ├── ColumnProfile.svelte
│ ├── DFProfile.svelte
│ ├── ProfileView.ts
│ ├── Profiler.svelte
│ ├── actions
│ │ ├── outline.ts
│ │ └── scrub-action-factory.ts
│ ├── create-chart
│ │ ├── AddChartButton.svelte
│ │ ├── BivariateChartEntry.svelte
│ │ ├── BivariateWrapper.svelte
│ │ ├── CreateChartMenu.svelte
│ │ └── DropdownMenu.svelte
│ ├── data-types
│ │ ├── Base.svelte
│ │ ├── DataTypeIcon.svelte
│ │ ├── FormattedDataType.svelte
│ │ ├── Interval.svelte
│ │ ├── Number.svelte
│ │ ├── Timestamp.svelte
│ │ ├── Varchar.svelte
│ │ ├── duckdb-data-types.ts
│ │ └── pandas-data-types.ts
│ ├── export-code
│ │ ├── ExportChartButton.svelte
│ │ ├── ExportFactButton.svelte
│ │ └── ExportableCode.ts
│ ├── fact-panel
│ │ ├── DisplayFact.svelte
│ │ ├── NumericalStats.svelte
│ │ ├── StringStats.svelte
│ │ ├── TempStats.svelte
│ │ └── VizOrStats.svelte
│ ├── icons
│ │ ├── AlertIcon.svelte
│ │ ├── BivariateButton.svelte
│ │ ├── BivariateIcon.svelte
│ │ ├── BooleanType.svelte
│ │ ├── Cancel.svelte
│ │ ├── CaretDownIcon.svelte
│ │ ├── CategoricalType.svelte
│ │ ├── Delete.svelte
│ │ ├── Done.svelte
│ │ ├── EditIcon.svelte
│ │ ├── ExportIcon.svelte
│ │ ├── FloatType.svelte
│ │ ├── IntegerType.svelte
│ │ ├── List.svelte
│ │ ├── Parquet.svelte
│ │ ├── Pin.svelte
│ │ ├── RefreshIcon.svelte
│ │ ├── RightArrow.svelte
│ │ ├── SettingsIcon.svelte
│ │ └── TimestampType.svelte
│ ├── nav
│ │ ├── CollapsibleCard.svelte
│ │ ├── ExpanderButton.svelte
│ │ ├── RefreshData.svelte
│ │ └── SettingsMenu.svelte
│ ├── tooltip
│ │ ├── FloatingElement.svelte
│ │ ├── Portal.svelte
│ │ ├── Tooltip.svelte
│ │ ├── TooltipContent.svelte
│ │ ├── types.d.ts
│ │ └── utils.ts
│ ├── utils
│ │ ├── constants.ts
│ │ ├── convertTypes.ts
│ │ ├── formatters.ts
│ │ ├── guid.ts
│ │ ├── sizes.ts
│ │ └── sort-utils.ts
│ └── viz
│ │ ├── BarAndLabel.svelte
│ │ ├── ExploreChart.svelte
│ │ ├── TopKSummary.svelte
│ │ ├── bivariate
│ │ ├── Area.svelte
│ │ ├── AxisX.svelte
│ │ ├── AxisY.svelte
│ │ ├── Bar.svelte
│ │ ├── BiHistogram.svelte
│ │ ├── BiLine.svelte
│ │ ├── Column.svelte
│ │ └── Line.svelte
│ │ ├── histogram
│ │ ├── HistogramBase.svelte
│ │ ├── NumericHistogram.svelte
│ │ ├── SmallHistogram.svelte
│ │ ├── SummaryStatLabel.svelte
│ │ ├── TimestampHistogram.svelte
│ │ └── histogram-tooltip
│ │ │ └── HistogramTooltip.svelte
│ │ └── timestamp
│ │ ├── TimestampBound.svelte
│ │ ├── TimestampDetail.svelte
│ │ ├── TimestampMouseoverAnnotation.svelte
│ │ ├── TimestampPaths.svelte
│ │ ├── TimestampProfileSummary.svelte
│ │ ├── ZoomWindow.svelte
│ │ ├── extremum-resolution-store.ts
│ │ └── utils.ts
├── dataAPI
│ ├── ProfileModel.ts
│ └── jupyter
│ │ ├── PythonExecutor.ts
│ │ ├── cell.ts
│ │ └── notebook.ts
├── index.ts
├── stores.ts
└── svg.d.ts
├── style
├── base.css
├── index.css
├── index.js
└── logo.svg
├── tailwind.config.cjs
├── tsconfig.json
└── webpack.config.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | coverage
4 | **/*.d.ts
5 | tests
6 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | 'eslint:recommended',
4 | 'plugin:@typescript-eslint/eslint-recommended',
5 | 'plugin:@typescript-eslint/recommended',
6 | 'plugin:prettier/recommended'
7 | ],
8 | parser: '@typescript-eslint/parser',
9 | parserOptions: {
10 | project: 'tsconfig.json',
11 | sourceType: 'module'
12 | },
13 | plugins: ['@typescript-eslint', 'svelte3'],
14 | rules: {
15 | '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }],
16 | '@typescript-eslint/no-explicit-any': 'off',
17 | '@typescript-eslint/no-namespace': 'off',
18 | '@typescript-eslint/no-use-before-define': 'off',
19 | '@typescript-eslint/quotes': [
20 | 'error',
21 | 'single',
22 | { avoidEscape: true, allowTemplateLiterals: false }
23 | ],
24 | curly: ['error', 'all'],
25 | eqeqeq: 'error',
26 | 'prefer-arrow-callback': 'error'
27 | },
28 | overrides: [
29 | {
30 | files: ['**/*.svelte'],
31 | processor: 'svelte3/svelte3'
32 | }
33 | ]
34 | };
35 |
--------------------------------------------------------------------------------
/.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 | **OS, browser, jupyter, and AutoProfiler versions (please complete the following information):**
27 | - OS: [e.g. Chrome]
28 | - Browser [e.g. chrome, safari]
29 | - AutoProfiler version [e.g. 0.2.2]:
30 | - Jupyter version:
31 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: feature
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Please describe your use case and feature request**
11 | Screenshots are appreciated if applicable!
12 |
13 | If you have general feedback about your experience using AutoProfiler or would be willing to chat with us about your use case please consider filling out our feedback form: https://forms.gle/V3ejpXxMcQXqYJG48
14 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Functionality
2 |
3 |
4 |
5 | ## Issues addressed
6 |
7 |
--------------------------------------------------------------------------------
/.github/screenshots/Autoprofiler_social_tag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmudig/AutoProfiler/32f612c23d1056bf8797fc4ac515cac1a35ac1fe/.github/screenshots/Autoprofiler_social_tag.png
--------------------------------------------------------------------------------
/.github/screenshots/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmudig/AutoProfiler/32f612c23d1056bf8797fc4ac515cac1a35ac1fe/.github/screenshots/demo.gif
--------------------------------------------------------------------------------
/.github/screenshots/profiler_sc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmudig/AutoProfiler/32f612c23d1056bf8797fc4ac515cac1a35ac1fe/.github/screenshots/profiler_sc.png
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches: main
6 | pull_request:
7 | branches: '*'
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v2
15 |
16 | - name: Base Setup
17 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
18 |
19 | - name: Install dependencies
20 | run: python -m pip install -U jupyterlab~=3.1 check-manifest
21 |
22 | - name: Build the extension
23 | run: |
24 | set -eux
25 | jlpm
26 | python -m pip install .
27 |
28 | jupyter labextension list 2>&1 | grep -ie "AutoProfiler.*OK"
29 | python -m jupyterlab.browser_check
30 |
31 | check-manifest -v
32 |
33 | pip install build
34 | python -m build --sdist
35 | cp dist/*.tar.gz myextension.tar.gz
36 | pip uninstall -y "digautoprofiler" jupyterlab
37 | rm -rf myextension
38 |
39 | - uses: actions/upload-artifact@v2
40 | with:
41 | name: myextension-sdist
42 | path: myextension.tar.gz
43 |
44 | test_isolated:
45 | needs: build
46 | runs-on: ubuntu-latest
47 |
48 | steps:
49 | - name: Checkout
50 | uses: actions/checkout@v2
51 | - name: Install Python
52 | uses: actions/setup-python@v2
53 | with:
54 | python-version: '3.8'
55 | architecture: 'x64'
56 | - uses: actions/download-artifact@v2
57 | with:
58 | name: myextension-sdist
59 | - name: Install and Test
60 | run: |
61 | set -eux
62 | # Remove NodeJS, twice to take care of system and locally installed node versions.
63 | sudo rm -rf $(which node)
64 | sudo rm -rf $(which node)
65 | pip install myextension.tar.gz
66 | pip install jupyterlab
67 | jupyter labextension list 2>&1 | grep -ie "AutoProfiler.*OK"
68 | python -m jupyterlab.browser_check --no-chrome-test
--------------------------------------------------------------------------------
/.github/workflows/deploy_juplite.yml:
--------------------------------------------------------------------------------
1 | name: Build and Deploy JupyterLite
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - '*'
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v2
17 | - name: Setup Python
18 | uses: actions/setup-python@v2
19 | with:
20 | python-version: 3.8
21 | - name: Install the dependencies
22 | run: |
23 | python -m pip install -r jupyterlite-requirements.txt
24 | python -m pip install -r requirements.txt
25 | - name: Build the JupyterLite site
26 | run: |
27 | jupyter lite build --contents examples --output-dir jlExamplesDist
28 | - name: Upload artifact
29 | uses: actions/upload-pages-artifact@v1
30 | with:
31 | path: ./jlExamplesDist
32 |
33 | deploy:
34 | needs: build
35 | if: github.ref == 'refs/heads/main'
36 | permissions:
37 | pages: write
38 | id-token: write
39 |
40 | environment:
41 | name: github-pages
42 | url: ${{steps.deployment.outputs.page_url}}
43 |
44 | runs-on: ubuntu-latest
45 | steps:
46 | - name: Deploy to GitHub Pages
47 | id: deployment
48 | uses: actions/deploy-pages@v1
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.bundle.*
2 | lib/
3 | node_modules/
4 | .eslintcache
5 | .stylelintcache
6 | *.egg-info/
7 | .ipynb_checkpoints
8 | *.tsbuildinfo
9 | digautoprofiler/labextension
10 |
11 | # Created by https://www.gitignore.io/api/python
12 | # Edit at https://www.gitignore.io/?templates=python
13 |
14 | ### Python ###
15 | # Byte-compiled / optimized / DLL files
16 | __pycache__/
17 | *.py[cod]
18 | *$py.class
19 |
20 | # C extensions
21 | *.so
22 |
23 | # Distribution / packaging
24 | .Python
25 | build/
26 | develop-eggs/
27 | dist/
28 | downloads/
29 | eggs/
30 | .eggs/
31 | lib/
32 | lib64/
33 | parts/
34 | sdist/
35 | var/
36 | wheels/
37 | pip-wheel-metadata/
38 | share/python-wheels/
39 | .installed.cfg
40 | *.egg
41 | MANIFEST
42 |
43 | # PyInstaller
44 | # Usually these files are written by a python script from a template
45 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
46 | *.manifest
47 | *.spec
48 |
49 | # Installer logs
50 | pip-log.txt
51 | pip-delete-this-directory.txt
52 |
53 | # Unit test / coverage reports
54 | htmlcov/
55 | .tox/
56 | .nox/
57 | .coverage
58 | .coverage.*
59 | .cache
60 | nosetests.xml
61 | coverage.xml
62 | *.cover
63 | .hypothesis/
64 | .pytest_cache/
65 |
66 | # Translations
67 | *.mo
68 | *.pot
69 |
70 | # Scrapy stuff:
71 | .scrapy
72 |
73 | # Sphinx documentation
74 | docs/_build/
75 |
76 | # PyBuilder
77 | target/
78 |
79 | # pyenv
80 | .python-version
81 |
82 | # celery beat schedule file
83 | celerybeat-schedule
84 |
85 | # SageMath parsed files
86 | *.sage.py
87 |
88 | # Spyder project settings
89 | .spyderproject
90 | .spyproject
91 |
92 | # Rope project settings
93 | .ropeproject
94 |
95 | # Mr Developer
96 | .mr.developer.cfg
97 | .project
98 | .pydevproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 | .dmypy.json
106 | dmypy.json
107 |
108 | # Pyre type checker
109 | .pyre/
110 |
111 | # End of https://www.gitignore.io/api/python
112 |
113 | # OSX files
114 | .DS_Store
115 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | **/node_modules
3 | **/lib
4 | **/package.json
5 | digautoprofiler
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "none",
4 | "arrowParens": "avoid",
5 | "tabWidth": 4,
6 | "plugins": [
7 | "prettier-plugin-svelte"
8 | ]
9 | }
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "stylelint-config-recommended",
4 | "stylelint-config-standard",
5 | "stylelint-prettier/recommended"
6 | ],
7 | "rules": {
8 | "property-no-vendor-prefix": null,
9 | "selector-no-vendor-prefix": null,
10 | "value-no-vendor-prefix": null,
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.codeActionsOnSave": {
3 | "source.fixAll.eslint": true
4 | },
5 | "eslint.validate": [
6 | "javascript",
7 | "typescript"
8 | ]
9 | }
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Overview of AutoProfiler development
2 |
3 | AutoProfiler is a jupyter extension. It contains frontend typescript code and python code for executing pandas commands. These are bundled together into one jupyter extension that can be pip installed by a user.
4 |
5 | ## Frontend Code
6 |
7 | The frontend svelte code is contained in the `src/` directory. We use the [svelte](https://learn.svelte.dev/tutorial/welcome-to-svelte) framework for components and do styling with [tailwind](https://tailwindcss.com/).
8 |
9 | - [`index.ts`](src/index.ts) is the entrypoint for the extension that jupyter looks at to load the extension.
10 | - The code roughly follows a Model, View, Controller pattern because that is how the jupyter extension examples are structured.
11 | - [`ProfilePanel.ts`](src/ProfilePanel.ts) -- main wrapper that creates a [`ProfileModel`](src/dataAPI/ProfileModel.ts) and [`ProfileView`](src/components/ProfileView.ts) and listens for changes to the notebook.
12 | - [`ProfileView`](src/components/ProfileView.ts) creates a [`Profiler.svelte`](src/components/Profiler.svelte) component that is the root component.
13 | - [`ProfileModel`](src/dataAPI/ProfileModel.ts) handles jupyter interactions to execute python code in the kernel and listen for changes to the notebook.
14 |
15 | ## Python code
16 |
17 | The python code is contained in the `digautoprofiler/` directory. This is mostly utility functions that are called by the frontend code to execute pandas commands and return the results, along with automatically generated jupyter extension code in the `digautoprofiler/labextension` directory (don't manually edit this).
18 |
19 | # Development Setup and workflow
20 |
21 | To run AutoProfiler locally you need to install as a python library that is editable.
22 |
23 | ## First time
24 |
25 | - You will need [NodeJS and npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) installed to build the extension package.
26 | - It is best to make a new conda environment before following steps (e.g. `conda create --name autoprofiler python=3.11`)
27 |
28 | Then pip install the following:
29 |
30 | ```bash
31 | pip install jupyterlab jupyter-packaging
32 | ```
33 |
34 | Now download and build extension:
35 |
36 | ```bash
37 | git clone https://github.com/cmudig/AutoProfiler.git
38 | cd AutoProfiler
39 | # Install package in development mode
40 | pip install -e .
41 | # Link your development version of the extension with JupyterLab
42 | jupyter labextension develop . --overwrite
43 | # Build Typescript source after making changes
44 | npm run build
45 | ```
46 |
47 | Jupyter labextension can be weird sometimes; nuking the conda env and restarting tends to fix it.
48 |
49 | ## Devloop: changes to frontend code in `src/`
50 |
51 | You can watch the source directory and run JupyterLab at the same time in different terminals to watch for changes in the extension's source and automatically rebuild the extension.
52 |
53 | ### Terminal 1
54 |
55 | Watch the source directory in one terminal, automatically rebuilding when changes are made to `src/`. You will need to refresh the browser to see changes.
56 |
57 | ```bash
58 | cd AutoProfiler
59 | npm run watch
60 | ```
61 |
62 | **Warning**: for some reason tailwind is not automatically re-loading classes during watch mode; if you add in new classes you will need to kill and restart the watch process or run `npm run build` to see changes.
63 |
64 | ### Terminal 2
65 |
66 | To test your code you can run a jupyter lab instance in another terminal. **Note**: this does NOT have to be in the same directory (I would encourage you to have separate testing directory); as long as the conda environment is the same, any update to the `digautoprofiler` package will be reflected in the running jupyter lab instance.
67 |
68 | ```bash
69 | cd /path/to/your/testing/directory
70 | conda activate autoprofiler
71 | jupyter lab
72 | ```
73 |
74 | ## Devloop: changes to python code in `digautoprofiler/`
75 |
76 | Changes to the python utility functions will update when the python module reloads. If you restart the kernel in jupyter, this should update the python package (you don't even have to refresh the page, just kill and restart kernel).
77 |
78 | ## Development uninstall
79 |
80 | ```bash
81 | pip uninstall digautoprofiler
82 | ```
83 |
84 | In development mode, you will also need to remove the symlink created by `jupyter labextension develop`
85 | command. To find its location, you can run `jupyter labextension list` to figure out where the `labextensions`
86 | folder is located. Then you can remove the symlink named `digautoprofiler` within that folder.
87 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2022, Will Epperson
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE
2 | include *.md
3 | include pyproject.toml
4 |
5 | include package.json
6 | include install.json
7 | include ts*.json
8 | include yarn.lock
9 | include requirements.txt
10 | include jupyterlite-requirements.txt
11 | include environment.yml
12 |
13 | graft digautoprofiler/labextension
14 |
15 | # Javascript files
16 | graft src
17 | graft style
18 | prune **/node_modules
19 | prune lib
20 | prune binder
21 |
22 | # extra rules to make match vc
23 | graft examples
24 | prune .vscode
25 |
26 | # Patterns to exclude from any directory
27 | global-exclude *~
28 | global-exclude *.pyc
29 | global-exclude *.pyo
30 | global-exclude .git
31 | global-exclude .ipynb_checkpoints
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Profile your [Pandas](https://pandas.pydata.org) Dataframes! Autoprofiler will automatically visualize your Pandas dataframes after every execution, no extra code necessary.
16 |
17 | Autoprofiler allows you to spend less time specifying charts and more time interacting with your data by automatically showing you profiling information like:
18 |
19 | - Distribution of each column
20 | - Sample values
21 | - Summary statistics
22 |
23 | ## Updates profiles as your data updates
24 |
25 | 
26 |
27 | Autoprofiler reads your current Jupyter notebook and produces profiles for the Pandas Dataframes in your memory as they change.
28 |
29 | https://user-images.githubusercontent.com/13400543/199877605-ba50f9c8-87e5-46c9-8207-1c6496bb3b18.mov
30 |
31 | ## Install
32 |
33 | To instally locally use pip and then open jupyter lab and the extension will be running.
34 |
35 | ```bash
36 | pip install -U digautoprofiler
37 | ```
38 |
39 | Please note, AutoProfiler only works in [JupyterLab](https://jupyter.org/install) with version >=3.x, < 4.0.0.
40 |
41 | ## Try it out
42 |
43 | To try out Autoprofiler in a hosted notebook, use one of the options below
44 |
45 | | Jupyter Lite | Binder |
46 | | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------: |
47 | | [](http://dig.cmu.edu/AutoProfiler) | [](https://mybinder.org/v2/gh/cmudig/AutoProfiler/HEAD?labpath=examples%2FSF%20Housing%20Demo.ipynb) |
48 |
49 | **Browser support:** AutoProfiler has been developed and tested with Chrome.
50 |
51 | ## Development Install
52 |
53 | For development install instructions, see [CONTRIBUTING.md](CONTRIBUTING.md).
54 |
55 | If you're having install issues, see [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
56 |
57 | ## Acknowledgements
58 |
59 | Big thanks to the Rill Data team! Much of our profiler UI code is adapted from [Rill Developer](https://github.com/rilldata/rill-developer).
60 |
61 | ## Citation
62 |
63 | Please reference our [VIS'23 paper](https://arxiv.org/abs/2308.03964):
64 |
65 | ```bibtex
66 | @article{epperson23autoprofiler,
67 | title={Dead or Alive: Continuous Data Profiling for Interactive Data Science},
68 | author={Will Epperson and Vaishnavi Goranla and Dominik Moritz and Adam Perer},
69 | journal={IEEE Transactions on Visualization and Computer Graphics},
70 | year={2023},
71 | url={https://arxiv.org/abs/2308.03964}
72 | }
73 | ```
74 |
75 | ## Let us know what you think! 📢
76 |
77 | We would love to hear your feedback on how you are using AutoProfiler! Please fill out [this form](https://forms.gle/V3ejpXxMcQXqYJG48) or email Will at [willepp@cmu.edu](mailto:willepp@cmu.edu).
78 |
--------------------------------------------------------------------------------
/RELEASE.md:
--------------------------------------------------------------------------------
1 | # Making a new release of AutoProfiler
2 |
3 | The extension can be published to `PyPI` and `npm` manually or using the [Jupyter Releaser](https://github.com/jupyter-server/jupyter_releaser).
4 |
5 | ## Manual release
6 |
7 | ### Python package
8 |
9 | This extension can be distributed as Python
10 | packages. All of the Python
11 | packaging instructions in the `pyproject.toml` file to wrap your extension in a
12 | Python package. Before generating a package, we first need to install `build`.
13 |
14 | ```bash
15 | pip install build twine
16 | ```
17 |
18 | To create a Python source package (`.tar.gz`) and the binary package (`.whl`) in the `dist/` directory, do:
19 |
20 | ```bash
21 | npm install
22 | npm run build
23 | python3 -m build
24 | ```
25 |
26 |
27 |
28 | Then to upload the package to PyPI, do:
29 |
30 | ```bash
31 | twine upload dist/*
32 | ```
33 |
34 | ### NPM package
35 |
36 | To publish the frontend part of the extension as a NPM package, do:
37 |
38 | ```bash
39 | npm login
40 | npm publish --access public
41 | ```
42 |
43 |
58 |
59 | ## Publishing to `conda-forge`
60 |
61 | If the package is not on conda forge yet, check the documentation to learn how to add it: https://conda-forge.org/docs/maintainer/adding_pkgs.html
62 |
63 | Otherwise a bot should pick up the new version publish to PyPI, and open a new PR on the feedstock repository automatically.
64 |
--------------------------------------------------------------------------------
/TROUBLESHOOTING.md:
--------------------------------------------------------------------------------
1 | # Troubleshooting
2 |
3 | Installing Jupyter extensions can be tricky, these are some common issues we've run into. If your issue isn't covered here please [open an issue](https://github.com/cmudig/AutoProfiler/issues) in the repo and we'll try to help you out.
4 |
5 | ### How do I load this into a fresh conda env?
6 |
7 | ```bash
8 | conda create -n autoprofiler-env python
9 | conda activate digautoprofiler-env
10 | pip install digautoprofiler
11 | ```
12 |
13 | ### Why isn't anything showing up?
14 |
15 | After pip installing, if you're not seeing AutoProfiler in the side bar then the extension was not installed properly. Jupyter reads extensions from a directory wherever it is installed.
16 |
17 | Check 1: Run the command below in your terminal.
18 |
19 | ```bash
20 | jupyter labextension list
21 | ```
22 |
23 | All of your extensions should list in the terminal like
24 |
25 | ```bash
26 | digautoprofiler v0.1.1 enabled OK (python, digautoprofiler)
27 | ```
28 |
29 | If this is not showing, then the extension did not install to the same location that jupyter is looking for. One way to check this is to run
30 |
31 | ```bash
32 | which jupyter
33 | which pip
34 | ```
35 |
36 | These should point to the same conda env if you're using conda. For example, if you made the conda env from earlier in the troubleshooting then it might be `~/opt/anaconda3/envs/digautoprofiler-env/bin/`
37 |
38 | Check 2: Another way to make sure the extension is installed is to run jupyter and then click on the puzzle piece in the left side bar. This shows all your activate extensions. These should be allowed to run and digautoprofiler should be listed.
39 |
--------------------------------------------------------------------------------
/digautoprofiler/__init__.py:
--------------------------------------------------------------------------------
1 | import json
2 | from pathlib import Path
3 |
4 | from ._version import __version__
5 | from .profile_lib import *
6 |
7 | HERE = Path(__file__).parent.resolve()
8 |
9 | with (HERE / "labextension" / "package.json").open() as fid:
10 | data = json.load(fid)
11 |
12 |
13 | def _jupyter_labextension_paths():
14 | return [{
15 | "src": "labextension",
16 | "dest": data["name"]
17 | }]
--------------------------------------------------------------------------------
/digautoprofiler/_version.py:
--------------------------------------------------------------------------------
1 | import json
2 | from pathlib import Path
3 |
4 | __all__ = ["__version__"]
5 |
6 | def _fetchVersion():
7 | HERE = Path(__file__).parent.resolve()
8 |
9 | for settings in HERE.rglob("package.json"):
10 | try:
11 | with settings.open() as f:
12 | version = json.load(f)["version"]
13 | return (
14 | version.replace("-alpha.", "a")
15 | .replace("-beta.", "b")
16 | .replace("-rc.", "rc")
17 | )
18 | except FileNotFoundError:
19 | pass
20 |
21 | raise FileNotFoundError(f"Could not find package.json under dir {HERE!s}")
22 |
23 | __version__ = _fetchVersion()
24 |
--------------------------------------------------------------------------------
/digautoprofiler/utils.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 |
3 | def convertVC(vc: pd.Series, colName: str):
4 | # this behavior changed in pandas 2.0.0
5 | if pd.__version__ >= "2.0.0":
6 | return vc.reset_index().rename(columns={colName: "value"}).to_dict('records')
7 |
8 | return vc.reset_index().rename(
9 | columns={"index": "value",
10 | colName: "count"}).to_dict('records')
11 |
12 | def convertDescribe(statistics: pd.Series):
13 | s = statistics.to_dict()
14 |
15 | return {
16 | "min": s["min"],
17 | "q25": s["25%"],
18 | "q50": s["50%"],
19 | "q75": s["75%"],
20 | "max": s["max"],
21 | "mean": s["mean"],
22 | }
23 |
24 | def convertBinned(numVC: pd.Series, true_min):
25 | """
26 | numVC has interval index from binned value counts. Replace far left low
27 | with true min because pandas cuts below min
28 | """
29 | d = pd.DataFrame(
30 | {"low": numVC.index.left,
31 | "high": numVC.index.right,
32 | "count": numVC.values
33 | })
34 | d = d.reset_index().rename(columns={"index": "bucket"})
35 | d_dict = d.to_dict('records')
36 |
37 | if len(d_dict) > 0:
38 | d_dict[0]['low'] = true_min
39 |
40 | return d_dict
--------------------------------------------------------------------------------
/environment.yml:
--------------------------------------------------------------------------------
1 | name: digautoprofiler-env
2 | channels:
3 | - conda-forge
4 | dependencies:
5 | - python
6 | - pip
7 | - pip:
8 | - digautoprofiler
9 |
--------------------------------------------------------------------------------
/examples/SF Housing Demo.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "ed4ecfc3",
6 | "metadata": {},
7 | "source": [
8 | "# Autoprofiler demo: SF Housing"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "id": "ca93f31d-656d-4824-bca6-5d09df6957f0",
15 | "metadata": {},
16 | "outputs": [],
17 | "source": [
18 | "import pandas as pd"
19 | ]
20 | },
21 | {
22 | "cell_type": "markdown",
23 | "id": "15c95ba0-2bd8-4a38-9f31-770f3625cbd6",
24 | "metadata": {},
25 | "source": [
26 | "## What is AutoProfiler?\n",
27 | "AutoProfiler shows you data _profiles_ for any pandas dataframe in the left side bar of Jupyter. Whenever you load or update a dataframe, autoprofiler will show you information like distributions, common values, missing values etc. \n",
28 | "\n",
29 | "__To install and use locally: `pip install -U digautoprofiler`__\n",
30 | "\n",
31 | "## This demo\n",
32 | "This dataset is a sample of Bay Area rents from 2000-2018 gathered from [this site](https://www.katepennington.org/data). Try exploring the dataset and open **Autoprofiler** in the left panel to see profiling charts!"
33 | ]
34 | },
35 | {
36 | "cell_type": "markdown",
37 | "id": "1d2d08bf",
38 | "metadata": {},
39 | "source": [
40 | "If this demo is running in jupyterlite, we have to read the dataframe and install dependencies in a slighly different way, if not we can just load it normally:"
41 | ]
42 | },
43 | {
44 | "cell_type": "code",
45 | "execution_count": null,
46 | "id": "5fede952-b5bf-40a4-b080-9239eabd0ee0",
47 | "metadata": {},
48 | "outputs": [],
49 | "source": [
50 | "import sys\n",
51 | "\n",
52 | "if sys.platform == \"emscripten\":\n",
53 | " # running in jupyterlite, so we need to manually install dependencies\n",
54 | " import piplite\n",
55 | " await piplite.install('altair')\n",
56 | " await piplite.install('digautoprofiler')\n",
57 | "\n",
58 | " # read the data for jupyter lite\n",
59 | " import pyodide\n",
60 | " URL = \"https://raw.githubusercontent.com/cmudig/AutoProfiler/main/examples/df_housing_sample.csv\"\n",
61 | " df_housing = pd.read_csv(pyodide.http.open_url(URL))\n",
62 | "else:\n",
63 | " df_housing = pd.read_csv(\"./df_housing_sample.csv\")"
64 | ]
65 | },
66 | {
67 | "cell_type": "markdown",
68 | "id": "ba9bc295-df10-4e75-b426-e96b60f25466",
69 | "metadata": {},
70 | "source": [
71 | "The profile charts will update whenever the data updates, including changing the type:"
72 | ]
73 | },
74 | {
75 | "cell_type": "code",
76 | "execution_count": null,
77 | "id": "66938dcd-fd82-4bf2-9a7f-85cfd009d8e4",
78 | "metadata": {},
79 | "outputs": [],
80 | "source": [
81 | "df_housing[\"date\"] = pd.to_datetime(df_housing[\"date\"], format=\"%Y%m%d\")"
82 | ]
83 | },
84 | {
85 | "cell_type": "markdown",
86 | "id": "37fbb000-61ca-4dc6-a8bd-08fcbd9c7ffe",
87 | "metadata": {},
88 | "source": [
89 | "Or whenever a new dataframe is created:"
90 | ]
91 | },
92 | {
93 | "cell_type": "code",
94 | "execution_count": null,
95 | "id": "eca5c953-4d29-49f7-8866-c47582780ad5",
96 | "metadata": {},
97 | "outputs": [],
98 | "source": [
99 | "expensive_rents = df_housing[df_housing[\"price\"] > 5000]"
100 | ]
101 | },
102 | {
103 | "cell_type": "markdown",
104 | "id": "1db013fa",
105 | "metadata": {},
106 | "source": [
107 | "Try different pandas commands to...\n",
108 | "- See how your dataframes profile changes after a command\n",
109 | "- Compare dataframes\n",
110 | "- See most common values, distributions, cardinality, null values and more!"
111 | ]
112 | },
113 | {
114 | "cell_type": "code",
115 | "execution_count": null,
116 | "id": "7d9229d4-8407-46de-85e4-4032e16796f0",
117 | "metadata": {},
118 | "outputs": [],
119 | "source": []
120 | }
121 | ],
122 | "metadata": {
123 | "kernelspec": {
124 | "display_name": "Python 3.9.10 64-bit",
125 | "language": "python",
126 | "name": "python3"
127 | },
128 | "language_info": {
129 | "codemirror_mode": {
130 | "name": "ipython",
131 | "version": 3
132 | },
133 | "file_extension": ".py",
134 | "mimetype": "text/x-python",
135 | "name": "python",
136 | "nbconvert_exporter": "python",
137 | "pygments_lexer": "ipython3",
138 | "version": "3.9.10"
139 | },
140 | "vscode": {
141 | "interpreter": {
142 | "hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49"
143 | }
144 | }
145 | },
146 | "nbformat": 4,
147 | "nbformat_minor": 5
148 | }
149 |
--------------------------------------------------------------------------------
/install.json:
--------------------------------------------------------------------------------
1 | {
2 | "packageManager": "python",
3 | "packageName": "digautoprofiler",
4 | "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package digautoprofiler"
5 | }
--------------------------------------------------------------------------------
/jupyterlite-requirements.txt:
--------------------------------------------------------------------------------
1 | jupyterlite>=0.1.0b13
2 | digautoprofiler>=0.2.8
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "digautoprofiler",
3 | "version": "3.0.1",
4 | "description": "Automatically profile your pandas dataframes in jupyter lab.",
5 | "keywords": [
6 | "jupyter",
7 | "jupyterlab",
8 | "jupyterlab-extension"
9 | ],
10 | "homepage": "https://github.com/cmudig/AutoProfiler",
11 | "bugs": {
12 | "url": "https://github.com/cmudig/AutoProfiler/issues"
13 | },
14 | "license": "BSD-3-Clause",
15 | "author": {
16 | "name": "Will Epperson",
17 | "email": "willepp@live.com"
18 | },
19 | "files": [
20 | "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
21 | "style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}"
22 | ],
23 | "main": "lib/index.js",
24 | "types": "lib/index.d.ts",
25 | "style": "style/index.css",
26 | "repository": {
27 | "type": "git",
28 | "url": "https://github.com/cmudig/AutoProfiler.git"
29 | },
30 | "scripts": {
31 | "build": "jlpm build:lib && jlpm build:labextension:dev",
32 | "build:prod": "jlpm clean && npm ci && tsc && webpack --mode=production && jlpm build:labextension",
33 | "build:labextension": "jupyter labextension build .",
34 | "build:labextension:dev": "jupyter labextension build --development True .",
35 | "build:lib": "tsc && webpack --mode=development",
36 | "clean": "jlpm clean:lib clean:labextension",
37 | "clean:lib": "rimraf lib tsconfig.tsbuildinfo",
38 | "clean:lintcache": "rimraf .eslintcache .stylelintcache",
39 | "clean:labextension": "rimraf digautoprofiler/labextension",
40 | "clean:rest": "rimraf digautoprofiler.egg-info node_modules dist",
41 | "clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:lintcache && jlpm clean:rest",
42 | "eslint": "jlpm eslint:check --fix",
43 | "eslint:check": "eslint . --cache --ext .ts,.tsx",
44 | "install:extension": "npm ci && jlpm build",
45 | "lint": "jlpm stylelint && jlpm prettier && jlpm eslint",
46 | "lint:check": "jlpm stylelint:check && jlpm prettier:check && jlpm eslint:check",
47 | "prettier": "jlpm prettier:base --write --list-different",
48 | "prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md,.svelte}\"",
49 | "prettier:check": "jlpm prettier:base --check",
50 | "stylelint": "jlpm stylelint:check --fix",
51 | "stylelint:check": "stylelint --cache \"style/**/*.css\"",
52 | "watch": "run-p watch:src watch:labextension",
53 | "watch:src": "webpack --watch --mode=development",
54 | "watch:labextension": "jupyter labextension watch ."
55 | },
56 | "dependencies": {
57 | "@jupyterlab/application": "^3.4.8",
58 | "@jupyterlab/notebook": "^3.4.8",
59 | "d3-format": "^3.1.0",
60 | "d3-scale": "^4.0.2",
61 | "d3-shape": "^3.1.0",
62 | "d3-time": "^3.0.0",
63 | "d3-time-format": "^4.1.0",
64 | "layercake": "^7.2.2",
65 | "lodash": "^4.17.21"
66 | },
67 | "devDependencies": {
68 | "@babel/core": "^7.19.3",
69 | "@babel/preset-env": "^7.19.3",
70 | "@jupyterlab/builder": "^3.4.8",
71 | "@tsconfig/svelte": "^3.0.0",
72 | "@types/lodash": "^4.14.186",
73 | "@types/webpack-env": "^1.18.0",
74 | "@typescript-eslint/eslint-plugin": "^5.39.0",
75 | "@typescript-eslint/parser": "^5.39.0",
76 | "arquero": "^4.8.8",
77 | "autoprefixer": "^10.4.14",
78 | "css-loader": "^6.7.1",
79 | "eslint": "^8.24.0",
80 | "eslint-config-prettier": "^8.5.0",
81 | "eslint-plugin-prettier": "^4.2.1",
82 | "eslint-plugin-svelte3": "^4.0.0",
83 | "npm-run-all": "^4.1.5",
84 | "postcss": "^8.4.24",
85 | "prettier": "^2.7.1",
86 | "prettier-plugin-svelte": "^2.7.1",
87 | "rimraf": "^3.0.2",
88 | "source-map-loader": "^4.0.0",
89 | "style-loader": "^3.3.1",
90 | "stylelint": "^14.13.0",
91 | "stylelint-config-prettier": "^9.0.3",
92 | "stylelint-config-recommended": "^7.0.0",
93 | "stylelint-config-standard": "~25.0.0",
94 | "stylelint-prettier": "^2.0.0",
95 | "svelte": "^3.50.1",
96 | "svelte-collapse": "^0.1.1",
97 | "svelte-loader": "^3.1.3",
98 | "svelte-loading-spinners": "^0.1.7",
99 | "svelte-preprocess": "^4.10.7",
100 | "svg-url-loader": "^8.0.0",
101 | "tailwindcss": "^3.3.2",
102 | "ts-loader": "^9.4.1",
103 | "typescript": "^4.8.4",
104 | "webpack": "^5.74.0",
105 | "webpack-cli": "^4.10.0"
106 | },
107 | "sideEffects": [
108 | "style/*.css",
109 | "style/index.js"
110 | ],
111 | "styleModule": "style/index.js",
112 | "publishConfig": {
113 | "access": "public"
114 | },
115 | "jupyterlab": {
116 | "extension": true,
117 | "outputDir": "digautoprofiler/labextension",
118 | "sharedPackages": {
119 | "@jupyter-widgets/base": {
120 | "bundled": false,
121 | "singleton": true
122 | },
123 | "@jupyterlab/notebook": {
124 | "bundled": false,
125 | "singleton": true
126 | },
127 | "@lumino/widgets": {
128 | "bundled": false,
129 | "singleton": true
130 | }
131 | }
132 | },
133 | "jupyter-releaser": {
134 | "hooks": {
135 | "before-build-npm": [
136 | "python -m pip install jupyterlab~=3.1",
137 | "jlpm"
138 | ],
139 | "before-build-python": [
140 | "jlpm clean:all"
141 | ]
142 | }
143 | }
144 | }
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["jupyter_packaging~=0.10,<2", "jupyterlab~=3.1"]
3 | build-backend = "jupyter_packaging.build_api"
4 |
5 | [tool.jupyter-packaging.options]
6 | skip-if-exists = ["digautoprofiler/labextension/static/style.js"]
7 | ensured-targets = ["digautoprofiler/labextension/static/style.js", "digautoprofiler/labextension/package.json"]
8 |
9 | [tool.jupyter-packaging.builder]
10 | factory = "jupyter_packaging.npm_builder"
11 |
12 | [tool.jupyter-packaging.build-args]
13 | build_cmd = "build:prod"
14 | npm = ["jlpm"]
15 |
16 | [tool.check-manifest]
17 | ignore = ["digautoprofiler/labextension/**", "yarn.lock", ".*", "package-lock.json", "*.cjs", "*.config.js"]
18 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas>=1.4.2
2 | altair
3 | jupyterlab >=3,<4.0.0
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | """
2 | digautoprofiler setup
3 | """
4 | import json
5 | import sys
6 | from pathlib import Path
7 | # from os import path
8 |
9 | import setuptools
10 |
11 | HERE = Path(__file__).parent.resolve()
12 |
13 | # Get the package info from package.json
14 | pkg_json = json.loads((HERE / "package.json").read_bytes())
15 |
16 | with open((HERE / "requirements.txt")) as fp:
17 | install_requires = fp.read()
18 |
19 | # install_requires = [
20 | # "jupyterlab>=3.3.3",
21 | # "pandas>=1.4.2"
22 | # ]
23 |
24 | # The name of the project
25 | name = "digautoprofiler"
26 |
27 | lab_path = (HERE / pkg_json["jupyterlab"]["outputDir"])
28 |
29 | # Representative files that should exist after a successful build
30 | ensured_targets = [
31 | str(lab_path / "package.json"),
32 | str(lab_path / "static/style.js")
33 | ]
34 |
35 | labext_name = pkg_json["name"]
36 |
37 | data_files_spec = [
38 | ("share/jupyter/labextensions/%s" % labext_name, str(lab_path.relative_to(HERE)), "**"),
39 | ("share/jupyter/labextensions/%s" % labext_name, str("."), "install.json"),
40 | ]
41 |
42 | long_description = (HERE / "README.md").read_text()
43 |
44 | version = (
45 | pkg_json["version"]
46 | .replace("-alpha.", "a")
47 | .replace("-beta.", "b")
48 | .replace("-rc.", "rc")
49 | )
50 |
51 | setup_args = dict(
52 | name=name, # pypi name
53 | version=version,
54 | url=pkg_json["homepage"],
55 | author=pkg_json["author"]["name"],
56 | author_email=pkg_json["author"]["email"],
57 | description=pkg_json["description"],
58 | license=pkg_json["license"],
59 | license_file="LICENSE",
60 | long_description=long_description,
61 | long_description_content_type="text/markdown",
62 | packages=setuptools.find_packages(),
63 | install_requires=install_requires,
64 | zip_safe=False,
65 | include_package_data=True,
66 | python_requires=">=3.7",
67 | platforms="Linux, Mac OS X, Windows",
68 | keywords=["Jupyter", "JupyterLab", "JupyterLab3"],
69 | classifiers=[
70 | "License :: OSI Approved :: BSD License",
71 | "Programming Language :: Python",
72 | "Programming Language :: Python :: 3",
73 | "Programming Language :: Python :: 3.7",
74 | "Programming Language :: Python :: 3.8",
75 | "Programming Language :: Python :: 3.9",
76 | "Programming Language :: Python :: 3.10",
77 | "Framework :: Jupyter",
78 | "Framework :: Jupyter :: JupyterLab",
79 | "Framework :: Jupyter :: JupyterLab :: 3",
80 | "Framework :: Jupyter :: JupyterLab :: Extensions",
81 | "Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt",
82 | "Intended Audience :: Developers",
83 | "Intended Audience :: Science/Research",
84 | "Intended Audience :: Other Audience",
85 | "Topic :: Scientific/Engineering :: Information Analysis",
86 | "Topic :: Scientific/Engineering :: Visualization",
87 | ],
88 | )
89 |
90 | try:
91 | from jupyter_packaging import (
92 | wrap_installers,
93 | npm_builder,
94 | get_data_files
95 | )
96 | post_develop = npm_builder(
97 | build_cmd="install:extension", source_dir="src", build_dir=lab_path
98 | )
99 | setup_args["cmdclass"] = wrap_installers(post_develop=post_develop, ensured_targets=ensured_targets)
100 | setup_args["data_files"] = get_data_files(data_files_spec)
101 | except ImportError as e:
102 | import logging
103 | logging.basicConfig(format="%(levelname)s: %(message)s")
104 | logging.warning("Build tool `jupyter-packaging` is missing. Install it with pip or conda.")
105 | if not ("--name" in sys.argv or "--version" in sys.argv):
106 | raise e
107 |
108 | if __name__ == "__main__":
109 | setuptools.setup(**setup_args)
110 |
--------------------------------------------------------------------------------
/src/ProfilePanel.ts:
--------------------------------------------------------------------------------
1 | import { StackedPanel } from '@lumino/widgets';
2 | import type { ISessionContext } from '@jupyterlab/apputils';
3 | import type { Message } from '@lumino/messaging';
4 | import type { NotebookAPI } from './dataAPI/jupyter/notebook';
5 | import { ProfileModel } from './dataAPI/ProfileModel';
6 | import { ProfileView } from './components/ProfileView';
7 | import { LabIcon } from '@jupyterlab/ui-components';
8 | import appIconStr from '../style/logo.svg';
9 |
10 | export class ProfilePanel extends StackedPanel {
11 | constructor() {
12 | super();
13 | this.addClass('auto-profile-wrapper');
14 | this.id = 'auto-profile-app';
15 | this.title.caption = 'Autoprofiler'; // shown on hover
16 | this.title.iconClass = 'autoprofile-logo';
17 |
18 | const icon = new LabIcon({
19 | name: 'auto-profile-app:app-icon',
20 | svgstr: appIconStr
21 | });
22 |
23 | this.title.icon = icon;
24 |
25 | // MODEL init
26 | this._profileModel = new ProfileModel(this._sessionContext);
27 |
28 | // VIEW init
29 | this._profileView = new ProfileView(this._profileModel);
30 | this.addWidget(this._profileView);
31 | }
32 |
33 | // ~~~~~~~~~ Variables, getters, setters ~~~~~~~~~
34 | private _sessionContext: ISessionContext;
35 | private _profileModel: ProfileModel;
36 | private _profileView: ProfileView;
37 |
38 | get session(): ISessionContext {
39 | return this._sessionContext;
40 | }
41 |
42 | set session(session: ISessionContext) {
43 | this._sessionContext = session;
44 | }
45 |
46 | public async connectNotebook(notebook: NotebookAPI) {
47 | if (notebook.hasConnection) {
48 | this.session = notebook.panel.sessionContext;
49 | }
50 | await this._profileModel.connectNotebook(notebook, () => { return this.isVisible });
51 | }
52 |
53 | // ~~~~~~~~~ Lifecycle methods for closing panel ~~~~~~~~~
54 | dispose(): void {
55 | this._sessionContext.dispose();
56 | super.dispose();
57 | }
58 |
59 | protected onCloseRequest(msg: Message): void {
60 | super.onCloseRequest(msg);
61 | this.dispose();
62 | }
63 |
64 | /**
65 | * Called before the widget is made visible.
66 | * NOTE: when using beforeShow, this.isVisible is false during update.
67 | *
68 | * other useful state messages are onAfterShow,
69 | * onBeforeHide, onAfterHide.
70 | * @param msg
71 | */
72 | protected onBeforeShow(msg: Message): void {
73 | this._profileModel.updateAll();
74 | }
75 |
76 | }
--------------------------------------------------------------------------------
/src/common/exchangeInterfaces.ts:
--------------------------------------------------------------------------------
1 | export type IColTypeTuple = {
2 | colName: string;
3 | colType: string;
4 | colIsIndex: boolean;
5 | };
6 |
7 | export type Warning = {
8 | warnMsg: string;
9 | }
10 |
11 | export type IDFColMap = {
12 | [key: string]: {
13 | columns: IColTypeTuple[];
14 | python_id: string;
15 | warnings: Warning[]
16 | };
17 | };
18 |
19 | // data fetched from kernel for frontend
20 | export type IDFProfileWStateMap = {
21 | [dfname: string]: IDFProfileWState;
22 | } | undefined;
23 |
24 |
25 | export type IDFProfileData = {
26 | profile: ColumnProfileData[];
27 | shape: number[];
28 | dfName: string;
29 | }
30 |
31 |
32 | export type IDFProfileState = {
33 | lastUpdatedTime: number;
34 | isPinned: boolean;
35 | warnings: Warning[]
36 | }
37 |
38 | export type IDFProfileWState = IDFProfileData & IDFProfileState
39 |
40 | export type ColumnProfileData = IColTypeTuple & {
41 | nullCount: number,
42 | summary: AnySummary;
43 | }
44 |
45 | export type AnySummary = NumericSummary | CategoricalSummary | BoolSummary | TemporalSummary;
46 |
47 | export type NumericSummary = {
48 | summaryType: "numeric";
49 | histogram: IHistogram;
50 | quantMeta: IQuantMeta;
51 | }
52 |
53 | export type CategoricalSummary = { // string or boolean
54 | summaryType: "categorical";
55 | cardinality: number; // num unique
56 | topK: ValueCount[];
57 | stringMeta: IStringMeta;
58 | }
59 |
60 | export type BoolSummary = { // string or boolean
61 | summaryType: "boolean";
62 | cardinality: number; // num unique
63 | topK: ValueCount[];
64 | }
65 |
66 | export type TemporalSummary = {
67 | summaryType: "temporal";
68 | histogram: IHistogram;
69 | timeInterval: Interval;
70 | temporalMeta: ITemporalMeta;
71 | }
72 |
73 | // type guards
74 | export function isNumericSummary(s: AnySummary): s is NumericSummary {
75 | return (s as NumericSummary).summaryType === "numeric"
76 | }
77 |
78 | export function isCategoricalSummary(s: AnySummary): s is CategoricalSummary {
79 | return (s as CategoricalSummary).summaryType === "categorical"
80 | }
81 |
82 | export function isBooleanSummary(s: AnySummary): s is BoolSummary {
83 | return (s as BoolSummary).summaryType === "boolean"
84 | }
85 |
86 | export function isTemporalSummary(s: AnySummary): s is TemporalSummary {
87 | return (s as TemporalSummary).summaryType === "temporal"
88 | }
89 |
90 | // ~~~~~~~ Individual data type info ~~~~~~~
91 | export type IQuantMeta = {
92 | min: number;
93 | q25: number;
94 | q50: number;
95 | q75: number;
96 | max: number;
97 | mean: number;
98 | sd_outlier: number;
99 | iqr_outlier: number;
100 | sortedness: string;
101 | n_zero: number;
102 | n_positive: number;
103 | n_negative: number;
104 | };
105 |
106 | export type IStringMeta = {
107 | minLength: number;
108 | maxLength: number;
109 | meanLength: number;
110 | };
111 |
112 | export type ITemporalMeta = {
113 | sortedness: string;
114 | num_outliers: number;
115 | }
116 |
117 | export type TimeBin = {
118 | count: number;
119 | ts_start: Date;
120 | ts_end: Date;
121 | };
122 |
123 | export type ValueCount = {
124 | value: any;
125 | count: number;
126 | };
127 |
128 | export type IHistogramBin = {
129 | bucket: number;
130 | low: number;
131 | high: number;
132 | count: number;
133 | };
134 |
135 | export type IHistogram = IHistogramBin[];
136 |
137 | export type Interval = {
138 | months: number;
139 | days: number;
140 | micros: number;
141 | };
142 |
143 | export enum PreviewRollupInterval {
144 | ms = '1 millisecond',
145 | second = '1 second',
146 | minute = '1 minute',
147 | hour = '1 hour',
148 | day = '1 day',
149 | month = '1 month',
150 | year = '1 year'
151 | }
152 |
153 | // ~~~ Bivariate chart types ~~~
154 |
155 | export type BivariateTimestampBin = {
156 | 'period': string;
157 | 'value': number;
158 | 'bucket': number;
159 | }
160 |
161 | export type BivariateTimestampInfo = {
162 | data: BivariateTimestampBin[];
163 | timestep: TimeOffset;
164 | }
165 |
166 | export type TimeOffset = "Y" | "M" | "W" | "D" | "H" | "T" | "S";
167 | export type AggrType = "count" | "mean" | "sum" | "min" | "max";
168 |
169 | type BivariateDataBase = {
170 | aggrType: AggrType;
171 | xColumn: IColTypeTuple;
172 | yColumn: IColTypeTuple;
173 | filledOut: boolean;
174 | }
175 |
176 | export type IBivariateHistogramData = BivariateDataBase & {
177 | chartType: 'histogram';
178 | data: ValueCount[];
179 | }
180 |
181 | export type IBivariateLinechartData = BivariateDataBase & {
182 | chartType: 'linechart';
183 | data: BivariateTimestampBin[];
184 | }
185 |
186 | export type IBivariateData = IBivariateHistogramData | IBivariateLinechartData;
187 |
188 | export type ChartSelection = {
189 | x: ColumnProfileData;
190 | y: ColumnProfileData;
191 | }
192 |
--------------------------------------------------------------------------------
/src/components/ColumnEntry.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
50 |
51 |
52 |
53 |
54 |
55 |
60 |
--------------------------------------------------------------------------------
/src/components/ProfileView.ts:
--------------------------------------------------------------------------------
1 | import { Widget } from '@lumino/widgets';
2 | import type { ProfileModel } from '../dataAPI/ProfileModel';
3 | import Profiler from './Profiler.svelte';
4 |
5 | export class ProfileView extends Widget {
6 | constructor(model: ProfileModel) {
7 | super();
8 | this.addClass('auto-profile-app');
9 |
10 | new Profiler({
11 | target: this.node,
12 | props: {
13 | profileModel: model
14 | }
15 | });
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/actions/outline.ts:
--------------------------------------------------------------------------------
1 | /** this action appends another text DOM element
2 | * that gives an outlined / punched-out look to whatever
3 | * svg text node it is applied to. It will then listen to
4 | * any of the relevant attributes / the content itself
5 | * and update accordingly via a basic MutationObserver.
6 | */
7 |
8 | interface OutlineAction {
9 | destroy: () => void;
10 | }
11 |
12 | export function outline(
13 | node: SVGElement,
14 | args = { color: 'white' }
15 | ): OutlineAction {
16 | const enclosingSVG = node.ownerSVGElement;
17 |
18 | // create a clone of the element.
19 | const clonedElement = node.cloneNode(true) as SVGElement;
20 | node.parentElement.insertBefore(clonedElement, node);
21 | clonedElement.setAttribute('fill', args.color);
22 | clonedElement.style.fill = args.color;
23 | clonedElement.setAttribute('filter', 'url(#outline-filter)');
24 | // apply the filter to this svg element.
25 | let outlineFilter = enclosingSVG.querySelector('#outline-filter');
26 | if (outlineFilter === null) {
27 | outlineFilter = document.createElementNS(
28 | 'http://www.w3.org/2000/svg',
29 | 'filter'
30 | );
31 | outlineFilter.id = 'outline-filter';
32 |
33 | const morph = document.createElementNS(
34 | 'http://www.w3.org/2000/svg',
35 | 'feMorphology'
36 | );
37 | morph.setAttribute('operator', 'dilate');
38 | morph.setAttribute('radius', '2');
39 | morph.setAttribute('in', 'SourceGraphic');
40 | morph.setAttribute('result', 'THICKNESS');
41 |
42 | const composite = document.createElementNS(
43 | 'http://www.w3.org/2000/svg',
44 | 'feComposite'
45 | );
46 | composite.setAttribute('operator', 'out');
47 | composite.setAttribute('in', 'THICKNESS');
48 | composite.setAttribute('in2', 'SourceGraphic');
49 |
50 | outlineFilter.appendChild(morph);
51 | outlineFilter.appendChild(composite);
52 | enclosingSVG.prepend(outlineFilter);
53 | }
54 |
55 | const config = {
56 | attributes: true,
57 | childList: true,
58 | subtree: true,
59 | characterData: true
60 | };
61 | const observer = new MutationObserver(() => {
62 | clonedElement.setAttribute('x', node.getAttribute('x'));
63 | clonedElement.setAttribute('y', node.getAttribute('y'));
64 | clonedElement.setAttribute(
65 | 'text-anchor',
66 | node.getAttribute('text-anchor')
67 | );
68 | if (node.getAttribute('dx')) {
69 | clonedElement.setAttribute('dx', node.getAttribute('dx'));
70 | }
71 | if (node.getAttribute('dy')) {
72 | clonedElement.setAttribute('dy', node.getAttribute('dy'));
73 | }
74 | // clone any animations that may be applied via svelte transitions.
75 | clonedElement.style.animation = node.style.animation;
76 | // copy the contents of the node.
77 | clonedElement.innerHTML = node.innerHTML;
78 | });
79 | observer.observe(node, config);
80 |
81 | return {
82 | destroy() {
83 | clonedElement.remove();
84 | }
85 | };
86 | }
87 |
--------------------------------------------------------------------------------
/src/components/create-chart/AddChartButton.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
14 |
--------------------------------------------------------------------------------
/src/components/create-chart/BivariateChartEntry.svelte:
--------------------------------------------------------------------------------
1 |
15 |
16 | {#if biData.data.length !== 0}
17 | {#if biData.chartType === 'histogram'}
18 |
23 | {:else if biData.chartType === 'linechart'}
24 |
29 | {/if}
30 |
31 | {#if allowEdit}
32 |
33 |
34 |
43 | Edit
44 |
45 |
46 | {/if}
47 | {:else}
48 | No aggregated data to show for the columns.
49 | {/if}
50 |
--------------------------------------------------------------------------------
/src/components/create-chart/BivariateWrapper.svelte:
--------------------------------------------------------------------------------
1 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | XY chart
81 |
82 |
83 |
84 |
85 | {title}
86 |
87 |
88 |
89 | {#if active}
90 |
94 |
95 | {#if inEditMode}
96 |
97 | {
104 | handleColumnEdit(
105 | event.detail.xVariable,
106 | event.detail.yVariable
107 | );
108 | }}
109 | on:aggrEdit={event => {
110 | handleAggrChange(event.detail.aggrType);
111 | }}
112 | on:delete={() => {
113 | dispatch('delete');
114 | }}
115 | />
116 |
117 | {/if}
118 |
119 |
120 | {#if biData.filledOut}
121 |
{
125 | inEditMode = true;
126 | }}
127 | />
128 | {/if}
129 |
130 | {/if}
131 |
132 |
133 |
--------------------------------------------------------------------------------
/src/components/create-chart/CreateChartMenu.svelte:
--------------------------------------------------------------------------------
1 |
36 |
37 |
40 |
41 | {
45 | xVariable = event?.detail;
46 | dispatchColumnUpdate();
47 | }}
48 | clickable={true}
49 | title={'Column one'}
50 | />
51 |
52 | {
56 | yVariable = event?.detail;
57 | dispatchColumnUpdate();
58 | }}
59 | clickable={!_.isNil(xVariable)}
60 | filteringColumn={xVariable}
61 | title={'Column two'}
62 | />
63 |
64 |
78 |
79 |
80 |
81 |
82 |
83 |
93 | Done
94 |
95 |
96 |
97 |
98 |
107 | Delete
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/src/components/create-chart/DropdownMenu.svelte:
--------------------------------------------------------------------------------
1 |
85 |
86 |
87 |
101 |
102 |
--------------------------------------------------------------------------------
/src/components/data-types/Base.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 | {#if isNull}
11 | ∅ null
12 | {:else}
13 |
14 | {/if}
15 |
16 |
--------------------------------------------------------------------------------
/src/components/data-types/DataTypeIcon.svelte:
--------------------------------------------------------------------------------
1 |
34 |
35 |
45 |
--------------------------------------------------------------------------------
/src/components/data-types/FormattedDataType.svelte:
--------------------------------------------------------------------------------
1 |
30 |
31 |
37 | {#if value === undefined}
38 |
39 | {:else}
40 | {formatDataType(value, type)}
41 | {/if}
42 |
43 |
--------------------------------------------------------------------------------
/src/components/data-types/Interval.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/components/data-types/Number.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/components/data-types/Timestamp.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/components/data-types/Varchar.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/components/data-types/duckdb-data-types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Provides mappings from duckdb's data types to conceptual types we use in the application:
3 | * CATEGORICALS, NUMERICS, and TIMESTAMPS.
4 | */
5 |
6 | export const INTEGERS = new Set([
7 | 'BIGINT',
8 | 'HUGEINT',
9 | 'SMALLINT',
10 | 'INTEGER',
11 | 'TINYINT',
12 | 'UBIGINT',
13 | 'UINTEGER',
14 | 'UTINYINT',
15 | 'INT1',
16 | 'INT4',
17 | 'INT',
18 | 'SIGNED',
19 | 'SHORT'
20 | ]);
21 |
22 | export const FLOATS = new Set([
23 | 'DOUBLE',
24 | 'DECIMAL',
25 | 'FLOAT8',
26 | 'NUMERIC',
27 | 'FLOAT'
28 | ]);
29 |
30 | export const NUMERICS = new Set([...INTEGERS, ...FLOATS]);
31 | export const BOOLEANS = new Set(['BOOLEAN', 'BOOL', 'LOGICAL']);
32 | export const TIMESTAMPS = new Set(['TIMESTAMP', 'TIME', 'DATETIME', 'DATE']);
33 | export const INTERVALS = new Set(['INTERVAL']);
34 | export const CATEGORICALS = new Set([
35 | 'BYTE_ARRAY',
36 | 'VARCHAR',
37 | 'CHAR',
38 | 'BPCHAR',
39 | 'TEXT',
40 | 'STRING'
41 | ]);
42 |
43 | interface IColorTokens {
44 | textClass: string;
45 | bgClass: string;
46 | vizFillClass: string;
47 | vizStrokeClass: string;
48 | }
49 |
50 | export const CATEGORICAL_TOKENS: IColorTokens = {
51 | textClass: 'text-sky-800',
52 | bgClass: 'bg-sky-200',
53 | vizFillClass: 'fill-sky-800',
54 | vizStrokeClass: 'fill-sky-800'
55 | };
56 |
57 | export const NUMERIC_TOKENS: IColorTokens = {
58 | textClass: 'text-red-800',
59 | bgClass: 'bg-red-200',
60 | vizFillClass: 'fill-red-300',
61 | vizStrokeClass: 'stroke-red-300'
62 | };
63 |
64 | export const TIMESTAMP_TOKENS: IColorTokens = {
65 | textClass: 'text-teal-800',
66 | bgClass: 'bg-teal-200',
67 | vizFillClass: 'fill-teal-500',
68 | vizStrokeClass: 'stroke-teal-500'
69 | };
70 |
71 | export const INTERVAL_TOKENS: IColorTokens = TIMESTAMP_TOKENS;
72 |
73 | function setTypeTailwindStyles(
74 | list: string[],
75 | // a tailwind class, for now.
76 | colorTokens: IColorTokens
77 | ) {
78 | return list.reduce((acc, v) => {
79 | acc[v] = { ...colorTokens };
80 | return acc;
81 | }, {});
82 | }
83 |
84 | export const DATA_TYPE_COLORS = {
85 | ...setTypeTailwindStyles(Array.from(CATEGORICALS), CATEGORICAL_TOKENS),
86 | ...setTypeTailwindStyles(Array.from(NUMERICS), NUMERIC_TOKENS),
87 | ...setTypeTailwindStyles(Array.from(TIMESTAMPS), TIMESTAMP_TOKENS),
88 | ...setTypeTailwindStyles(Array.from(INTERVALS), INTERVAL_TOKENS),
89 | ...setTypeTailwindStyles(Array.from(BOOLEANS), CATEGORICAL_TOKENS)
90 | };
91 |
--------------------------------------------------------------------------------
/src/components/data-types/pandas-data-types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Provides mappings from pandas data types to conceptual types we use in the application:
3 | * CATEGORICALS, NUMERICS, and TIMESTAMPS.
4 | */
5 |
6 | // TODO use built in pandas type checking?
7 |
8 | export const INTEGERS = new Set([
9 | 'int',
10 | 'int8',
11 | 'int16',
12 | 'int32',
13 | 'int64',
14 | 'uint8',
15 | 'uint16',
16 | 'uint32',
17 | 'uint64',
18 | 'Int8',
19 | 'Int16',
20 | 'Int32',
21 | 'Int64',
22 | 'UInt8',
23 | 'UInt16',
24 | 'UInt32',
25 | 'UInt64' // unclear if these are necessary
26 | ]);
27 |
28 | export const FLOATS = new Set([
29 | 'float',
30 | 'float_',
31 | 'float16',
32 | 'float32',
33 | 'float64'
34 | ]);
35 |
36 | export const NUMERICS = new Set([...INTEGERS, ...FLOATS]);
37 | export const BOOLEANS = new Set(['bool', '_bool']);
38 | export const TIMESTAMPS = new Set(['datetime64', 'datetime64[ns]']);
39 | export const INTERVALS = new Set([]); // TODO add pandas.Interval support
40 | export const CATEGORICALS = new Set(['object', 'string', 'str', 'category']);
41 |
42 | interface IColorTokens {
43 | textClass: string;
44 | bgClass: string;
45 | vizFillClass: string;
46 | vizStrokeClass: string;
47 | vizHoverClass?: string;
48 | }
49 |
50 | export const CATEGORICAL_TOKENS: IColorTokens = {
51 | textClass: 'text-sky-800',
52 | bgClass: 'bg-sky-200',
53 | vizFillClass: 'fill-sky-800',
54 | vizStrokeClass: 'fill-sky-800'
55 | };
56 |
57 | export const NUMERIC_TOKENS: IColorTokens = {
58 | textClass: 'text-red-800',
59 | bgClass: 'bg-red-200',
60 | vizFillClass: 'fill-red-300',
61 | vizStrokeClass: 'stroke-red-300',
62 | vizHoverClass: 'fill-red-400'
63 | };
64 |
65 | export const TIMESTAMP_TOKENS: IColorTokens = {
66 | textClass: 'text-teal-800',
67 | bgClass: 'bg-teal-200',
68 | vizFillClass: 'fill-teal-500',
69 | vizStrokeClass: 'stroke-teal-500'
70 | };
71 |
72 | export const INTERVAL_TOKENS: IColorTokens = TIMESTAMP_TOKENS;
73 |
74 | function setTypeTailwindStyles(
75 | list: string[],
76 | // a tailwind class, for now.
77 | colorTokens: IColorTokens
78 | ) {
79 | return list.reduce((acc, v) => {
80 | acc[v] = { ...colorTokens };
81 | return acc;
82 | }, {});
83 | }
84 |
85 | export const DATA_TYPE_COLORS = {
86 | ...setTypeTailwindStyles(Array.from(CATEGORICALS), CATEGORICAL_TOKENS),
87 | ...setTypeTailwindStyles(Array.from(BOOLEANS), CATEGORICAL_TOKENS),
88 | ...setTypeTailwindStyles(Array.from(NUMERICS), NUMERIC_TOKENS),
89 | ...setTypeTailwindStyles(Array.from(TIMESTAMPS), TIMESTAMP_TOKENS),
90 | // ...setTypeTailwindStyles(Array.from(INTERVALS), INTERVAL_TOKENS),
91 | };
92 |
--------------------------------------------------------------------------------
/src/components/export-code/ExportChartButton.svelte:
--------------------------------------------------------------------------------
1 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
57 |
58 | Export chart to code
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/components/export-code/ExportFactButton.svelte:
--------------------------------------------------------------------------------
1 |
41 |
42 |
43 |
50 |
51 | {tooltipText}
52 |
53 |
--------------------------------------------------------------------------------
/src/components/fact-panel/DisplayFact.svelte:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/fact-panel/NumericalStats.svelte:
--------------------------------------------------------------------------------
1 |
48 |
49 |
50 |
51 |
52 | There {stats?.sd_outlier === 1 ? 'is' : 'are'}
53 |
54 | {stats?.sd_outlier} outlier{stats?.sd_outlier === 1 ? '' : 's'}
55 | more than 3 std away from the mean.
56 |
57 |
58 |
65 |
66 |
67 |
68 |
69 |
70 | There {stats?.iqr_outlier === 1 ? 'is' : 'are'}
71 |
72 | {stats?.iqr_outlier} outlier{stats?.iqr_outlier === 1
73 | ? ''
74 | : 's'}
75 | more than 1.5 * IQR below q1 or above q3.
76 |
77 |
78 |
85 |
86 |
87 |
88 |
89 |
90 | This column is {formatSort(stats?.sortedness)}.
91 |
92 |
93 |
94 |
95 |
96 | {formatNumberString(
97 | stats?.n_zero,
98 | stats?.n_positive,
99 | stats?.n_negative
100 | )}
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/src/components/fact-panel/StringStats.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 | Entries are {stats?.minLength}-{stats?.maxLength} characters long, mean
19 | of {formatFloat(stats?.meanLength)}.
20 |
21 |
22 |
23 |
24 |
25 | {formatFloat((unique / rows) * 100.0)}% of the entires are unique ({unique}
26 | out of {rows} non-null rows).
27 |
28 |
29 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/components/fact-panel/TempStats.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 | This column is {formatSort(facts?.sortedness)}.
17 |
18 |
19 |
20 |
21 |
22 | There are {facts?.num_outliers} time-series outliers with respect to
23 | count using the hampel identifier method.
24 |
25 |
26 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/components/icons/AlertIcon.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
18 |
--------------------------------------------------------------------------------
/src/components/icons/BivariateButton.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
22 |
--------------------------------------------------------------------------------
/src/components/icons/BivariateIcon.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
20 |
21 |
33 |
--------------------------------------------------------------------------------
/src/components/icons/BooleanType.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
17 |
--------------------------------------------------------------------------------
/src/components/icons/Cancel.svelte:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
23 |
--------------------------------------------------------------------------------
/src/components/icons/CaretDownIcon.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
22 |
--------------------------------------------------------------------------------
/src/components/icons/CategoricalType.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
16 |
--------------------------------------------------------------------------------
/src/components/icons/Delete.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
17 |
--------------------------------------------------------------------------------
/src/components/icons/Done.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
22 |
23 |
41 |
--------------------------------------------------------------------------------
/src/components/icons/EditIcon.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
20 |
--------------------------------------------------------------------------------
/src/components/icons/ExportIcon.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
41 |
--------------------------------------------------------------------------------
/src/components/icons/FloatType.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
21 |
--------------------------------------------------------------------------------
/src/components/icons/IntegerType.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
25 |
--------------------------------------------------------------------------------
/src/components/icons/List.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
17 |
--------------------------------------------------------------------------------
/src/components/icons/Parquet.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
17 |
--------------------------------------------------------------------------------
/src/components/icons/Pin.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
18 |
--------------------------------------------------------------------------------
/src/components/icons/RefreshIcon.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
17 |
--------------------------------------------------------------------------------
/src/components/icons/RightArrow.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
16 |
--------------------------------------------------------------------------------
/src/components/icons/SettingsIcon.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
19 |
--------------------------------------------------------------------------------
/src/components/icons/TimestampType.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
22 |
--------------------------------------------------------------------------------
/src/components/nav/CollapsibleCard.svelte:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
48 |
--------------------------------------------------------------------------------
/src/components/nav/ExpanderButton.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
25 |
--------------------------------------------------------------------------------
/src/components/nav/RefreshData.svelte:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
28 |
29 | Manually refresh data
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/components/nav/SettingsMenu.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
25 | {#if active}
26 |
27 |
28 |
33 |
41 |
42 |
43 |
44 | {/if}
45 |
46 |
47 |
71 |
--------------------------------------------------------------------------------
/src/components/tooltip/FloatingElement.svelte:
--------------------------------------------------------------------------------
1 |
9 |
117 |
118 |
119 |
120 |
121 |
129 |
130 |
131 |
132 |
--------------------------------------------------------------------------------
/src/components/tooltip/Portal.svelte:
--------------------------------------------------------------------------------
1 |
31 |
32 |
33 |
34 | {#if mounted}
35 |
36 | {/if}
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/components/tooltip/Tooltip.svelte:
--------------------------------------------------------------------------------
1 |
20 |
60 |
61 | {
65 | waitUntil(() => {
66 | active = true;
67 | }, activeDelay);
68 | }}
69 | on:mouseleave={() => {
70 | waitUntil(() => {
71 | active = false;
72 | }, nonActiveDelay);
73 | }}
74 | >
75 |
76 | {#if active && !suppress && !$childRequestedTooltipSuppression}
77 |
78 |
79 |
86 |
87 |
88 |
89 |
90 | {/if}
91 |
92 |
--------------------------------------------------------------------------------
/src/components/tooltip/TooltipContent.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/components/tooltip/types.d.ts:
--------------------------------------------------------------------------------
1 | export type Location = "left" | "right" | "top" | "bottom";
2 | export type Alignment = "start" | "middle" | "end";
3 | export type FloatingElementRelationship = "parent" | "direct" | "mouse";
4 |
--------------------------------------------------------------------------------
/src/components/tooltip/utils.ts:
--------------------------------------------------------------------------------
1 |
2 | function minmax(v, min, max) {
3 | return Math.max(min, Math.min(v, max));
4 | }
5 |
6 | export function mouseLocationToBoundingRect({ x, y, width = 0, height = 0 }) {
7 | return {
8 | width,
9 | height,
10 | left: x,
11 | right: x + width,
12 | top: y,
13 | bottom: y + height,
14 | };
15 |
16 | }
17 |
18 | export function placeElement({
19 | location,
20 | alignment,
21 | parentPosition, // using getBoundingClientRect // DOMRect
22 | elementPosition, // using getBoundingClientRect // DOMRect
23 | distance = 0,
24 | x = 0,
25 | y = 0,
26 | windowWidth = window.innerWidth,
27 | windowHeight = window.innerHeight,
28 | pad = 16 * 2,
29 | }) {
30 | let left;
31 | let top;
32 |
33 | const elementWidth = elementPosition.width;
34 | const elementHeight = elementPosition.height;
35 |
36 | const parentRight = parentPosition.right + x;
37 | const parentLeft = parentPosition.left + x;
38 | const parentTop = parentPosition.top + y;
39 | const parentBottom = parentPosition.bottom + y;
40 | const parentWidth = parentPosition.width;
41 | const parentHeight = parentPosition.height;
42 |
43 | // Task 1: check if we need to reflect agains the location axis.
44 | if (location === "bottom") {
45 | if (parentBottom + elementHeight + distance + pad > windowHeight + y) {
46 | top = parentTop - elementHeight - distance;
47 | } else {
48 | top = parentBottom + distance;
49 | }
50 | } else if (location === "top") {
51 | if (parentTop - elementHeight - distance - pad < y) {
52 | top = parentBottom + distance;
53 | } else {
54 | top = parentTop - elementHeight - distance;
55 | }
56 | } else if (location === "left") {
57 | if (parentLeft - distance - elementWidth - pad < x) {
58 | // reflect
59 | left = parentRight + distance;
60 | } else {
61 | left = parentLeft - elementWidth - distance;
62 | }
63 | } else if (location === "right") {
64 | if (parentRight + elementWidth + distance + pad > windowWidth + x) {
65 | left = parentLeft - elementWidth - distance;
66 | } else {
67 | left = parentRight + distance;
68 | }
69 | }
70 |
71 | // OUR SECOND JOB IS RE-ALIGNMENT ALONG THE ALIGNMENT ACTION.
72 | let alignmentValue;
73 |
74 | const rightLeft = location === "right" || location === "left";
75 |
76 | switch (alignment) {
77 | case "start": {
78 | alignmentValue = rightLeft
79 | ? parentTop // right / left
80 | : parentLeft; // top / bottom
81 | break;
82 | }
83 | case "end": {
84 | alignmentValue = rightLeft
85 | ? parentBottom - elementHeight // right / left
86 | : parentRight - elementWidth; // top / bottom
87 | break;
88 | }
89 | default: {
90 | // 'middle'
91 | alignmentValue = rightLeft
92 | ? parentTop - (elementHeight - parentHeight) / 2 // right / left
93 | : parentLeft - (elementWidth - parentWidth) / 2; // top / bottom
94 | break;
95 | }
96 | }
97 | const alignMin = pad + (rightLeft ? y : x);
98 | const alignMax = rightLeft
99 | ? y + windowHeight - elementHeight - pad
100 | : x + windowWidth - elementWidth - pad;
101 |
102 | const value = minmax(alignmentValue, alignMin, alignMax);
103 |
104 | if (rightLeft) {
105 | top = value;
106 | } else {
107 | left = value;
108 | }
109 |
110 | return [left, top];
111 | }
112 |
--------------------------------------------------------------------------------
/src/components/utils/constants.ts:
--------------------------------------------------------------------------------
1 | export interface DomainCoordinates {
2 | x: number; //| Date;
3 | y: number; //| Date;
4 | }
5 |
6 | export const DEFAULT_COORDINATES: DomainCoordinates = {
7 | x: undefined,
8 | y: undefined
9 | };
10 |
11 | export interface PlotConfig {
12 | top: number;
13 | bottom: number;
14 | left: number;
15 | right: number;
16 | buffer: number;
17 | width: number;
18 | height: number;
19 | devicePixelRatio: number;
20 | plotTop: number;
21 | plotBottom: number;
22 | plotLeft: number;
23 | plotRight: number;
24 | fontSize: number;
25 | textGap: number;
26 | id: any;
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/utils/convertTypes.ts:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 |
3 | import type { IHistogram, TimeBin } from '../../common/exchangeInterfaces';
4 |
5 | export function convertToTimeBin(histogram: IHistogram): TimeBin[] {
6 | if (histogram == undefined) return [];
7 |
8 | return histogram.map(b => ({
9 | count: b.count,
10 | ts_start: new Date(b.low * 1000),
11 | ts_end: new Date(b.high * 1000)
12 | }));
13 | }
--------------------------------------------------------------------------------
/src/components/utils/formatters.ts:
--------------------------------------------------------------------------------
1 | import {
2 | INTERVALS,
3 | INTEGERS,
4 | FLOATS,
5 | CATEGORICALS,
6 | TIMESTAMPS,
7 | BOOLEANS
8 | } from '../data-types/pandas-data-types';
9 | import {
10 | type Interval,
11 | PreviewRollupInterval
12 | } from '../../common/exchangeInterfaces';
13 | import { format } from 'd3-format';
14 | import { timeFormat } from 'd3-time-format';
15 |
16 | const zeroPad = format('02d');
17 | const msPad = format('03d');
18 | export const formatInteger = format(',');
19 | const formatRate = format('.1f');
20 |
21 | export function formatNumeric(type: string, value) {
22 | if (INTEGERS.has(type)) {
23 | return formatInt(value);
24 | } else if (FLOATS.has(type)) {
25 | return formatFloat(value);
26 | }
27 |
28 | return value
29 | }
30 |
31 |
32 | export function formatInt(v) {
33 | return format('d')(v)
34 | }
35 |
36 | export function formatFloat(v) {
37 | if (Math.abs(v) >= 1) {
38 | return v.toFixed(1);
39 | }
40 |
41 | // 2 significant digits
42 | return format('.2r')(v);
43 | }
44 |
45 | export const formatPercentage = format('.1%');
46 |
47 | /**
48 | * changes precision depending on the
49 | */
50 | export function formatBigNumberPercentage(v) {
51 | if (v < 0.0001) {
52 | const f = format('.4%')(v);
53 | if (f === '0.0000%') {
54 | return '~ 0%';
55 | } else {
56 | return f;
57 | }
58 | } else {
59 | return format('.2%')(v);
60 | }
61 | }
62 |
63 | export function removeTimezoneOffset(dt) {
64 | return new Date(dt.getTime() + dt.getTimezoneOffset() * 60000);
65 | }
66 |
67 | export const standardTimestampFormat = (v, type = 'TIMESTAMP') => {
68 | let fmt = timeFormat('%b %d, %Y %I:%M:%S');
69 | if (type === 'DATE') {
70 | fmt = timeFormat('%b %d, %Y');
71 | }
72 | return fmt(removeTimezoneOffset(new Date(v)));
73 | };
74 | export const datePortion = timeFormat('%b %d, %Y');
75 | export const timePortion = timeFormat('%I:%M:%S');
76 |
77 | export function microsToTimestring(microseconds: number) {
78 | // to format micros, we need to translate this to hh:mm:ss.
79 | // start with hours/
80 | const sign = Math.sign(microseconds);
81 | const micros = Math.abs(microseconds);
82 | const hours = ~~(micros / 1000 / 1000 / 60 / 60);
83 | let remaining = micros - hours * 1000 * 1000 * 60 * 60;
84 | const minutes = ~~(remaining / 1000 / 1000 / 60);
85 | //const seconds = (remaining - (minutes * 1000 * 1000 * 60)) / 1000 / 1000;
86 | remaining -= minutes * 1000 * 1000 * 60;
87 | const seconds = ~~(remaining / 1000 / 1000);
88 | remaining -= seconds * 1000 * 1000;
89 | const ms = ~~(remaining / 1000);
90 | if (hours === 0 && minutes === 0 && seconds === 0 && ms > 0) {
91 | return `${sign === 1 ? '' : '-'}${ms}ms`;
92 | }
93 | return `${sign === 1 ? '' : '-'}${zeroPad(hours)}:${zeroPad(
94 | minutes
95 | )}:${zeroPad(seconds)}.${msPad(ms)}`;
96 | }
97 |
98 | export function intervalToTimestring(interval: Interval) {
99 | const months = interval.months
100 | ? `${formatInteger(interval.months)} month${interval.months > 1 ? 's' : ''
101 | } `
102 | : '';
103 | const days = interval.days
104 | ? `${formatInteger(interval.days)} day${interval.days > 1 ? 's' : ''} `
105 | : '';
106 | const time =
107 | interval.months > 0 || interval.days > 1
108 | ? ''
109 | : microsToTimestring(interval.micros);
110 | // if only days && days > 365, convert to years?
111 | if (interval.months === 0 && interval.days > 0 && interval.days > 365) {
112 | return `${formatRate(interval.days / 365)} years`;
113 | }
114 | return `${months}${days}${time}`;
115 | }
116 |
117 | export function formatCompactInteger(n: number) {
118 | let fmt: any;
119 | if (n <= 1000) {
120 | fmt = formatInteger;
121 | return fmt(~~n);
122 | } else {
123 | fmt = format('.3s');
124 | return fmt(n);
125 | }
126 | }
127 |
128 | export function formatDataType(value: any, type: string) {
129 | if (INTEGERS.has(type)) {
130 | return value;
131 | } else if (FLOATS.has(type)) {
132 | return value;
133 | } else if (CATEGORICALS.has(type)) {
134 | return value;
135 | } else if (TIMESTAMPS.has(type)) {
136 | return standardTimestampFormat(value, type);
137 | } else if (INTERVALS.has(type)) {
138 | return intervalToTimestring(value);
139 | } else if (BOOLEANS.has(type)) {
140 | return value;
141 | }
142 | }
143 |
144 | /** These will be used in the string */
145 | export const PreviewRollupIntervalFormatter = {
146 | [PreviewRollupInterval.ms]:
147 | 'millisecond-level' /** showing rows binned by ms */,
148 | [PreviewRollupInterval.second]:
149 | 'second-level' /** showing rows binned by second */,
150 | [PreviewRollupInterval.minute]:
151 | 'minute-level' /** showing rows binned by minute */,
152 | [PreviewRollupInterval.hour]: 'hourly' /** showing hourly counts */,
153 | [PreviewRollupInterval.day]: 'daily' /** showing daily counts */,
154 | [PreviewRollupInterval.month]: 'monthly' /** showing monthly counts */,
155 | [PreviewRollupInterval.year]: 'yearly' /** showing yearly counts */
156 | };
157 |
158 | export function formatSort(sortedness: string) {
159 | if (sortedness === 'noSort') {
160 | return 'not sorted';
161 | }
162 | return `sorted (${sortedness})`;
163 | }
164 |
--------------------------------------------------------------------------------
/src/components/utils/guid.ts:
--------------------------------------------------------------------------------
1 | export function guidGenerator() {
2 | const S4 = function () {
3 | return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
4 | };
5 | return (
6 | S4() +
7 | S4() +
8 | '-' +
9 | S4() +
10 | '-' +
11 | S4() +
12 | '-' +
13 | S4() +
14 | '-' +
15 | S4() +
16 | S4() +
17 | S4()
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/utils/sizes.ts:
--------------------------------------------------------------------------------
1 | export const config = {
2 | nullPercentageWidth: 75,
3 | mediumCutoff: 300,
4 | compactBreakpoint: 350,
5 | largeBreakpoint: 500,
6 | summaryVizWidth: { default: 120, large: 240 },
7 | exampleWidth: { medium: 204, small: 132 }
8 | };
9 |
10 |
11 | export function getSummarySize(containerWidth: number): number {
12 | if (containerWidth > config.largeBreakpoint) {
13 | return config.summaryVizWidth.large;
14 | }
15 |
16 | return config.summaryVizWidth.default;
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/utils/sort-utils.ts:
--------------------------------------------------------------------------------
1 | import { BOOLEANS, CATEGORICALS, INTEGERS, FLOATS, TIMESTAMPS } from "../data-types/pandas-data-types";
2 | import type { IDFProfileWState } from "../../common/exchangeInterfaces"
3 |
4 | // SORT COLUMNS
5 | export function sortByCardinality(a, b) {
6 | if (a.summary && b.summary) {
7 | if (a.summary.cardinality < b.summary.cardinality) {
8 | return 1;
9 | } else if (a.summary.cardinality > b.summary.cardinality) {
10 | return -1;
11 | } else {
12 | return sortByType(a, b);
13 | }
14 | } else {
15 | return sortByType(a, b);
16 | }
17 | }
18 |
19 | export function sortByNullity(a, b) {
20 | if (a.nullCount !== undefined && b.nullCount !== undefined) {
21 | if (a.nullCount < b.nullCount) {
22 | return 1;
23 | } else if ((a.nullCount > b.nullCount)) {
24 | return -1;
25 | } else {
26 | const byType = sortByType(a, b);
27 | if (byType) return byType;
28 | return sortByName(a, b);
29 | }
30 | }
31 |
32 | return sortByName(a, b);
33 | }
34 |
35 | export function sortByType(a, b) {
36 | if (BOOLEANS.has(a.type) && !BOOLEANS.has(b.type)) return 1;
37 | else if (!BOOLEANS.has(a.type) && BOOLEANS.has(b.type)) return -1;
38 | else if (CATEGORICALS.has(a.type) && !CATEGORICALS.has(b.type)) return 1;
39 | else if (!CATEGORICALS.has(a.type) && CATEGORICALS.has(b.type)) return -1;
40 | else if (FLOATS.has(a.type) && !FLOATS.has(b.type)) return 1;
41 | else if (!FLOATS.has(a.type) && FLOATS.has(b.type)) return -1;
42 | else if (INTEGERS.has(a.type) && !INTEGERS.has(b.type)) return 1;
43 | else if (!INTEGERS.has(a.type) && INTEGERS.has(b.type)) return -1;
44 | else if (TIMESTAMPS.has(a.type) && TIMESTAMPS.has(b.type)) {
45 | return -1;
46 | } else if (!TIMESTAMPS.has(a.type) && TIMESTAMPS.has(b.type)) {
47 | return 1;
48 | }
49 | return 0;//sortByName(a, b);
50 | }
51 |
52 | export function sortByName(a, b) {
53 | return (a.name > b.name) ? 1 : -1;
54 | }
55 |
56 | export function defaultSort(a, b) {
57 | const byType = sortByType(a, b);
58 | if (byType !== 0) return byType;
59 | if (
60 | CATEGORICALS.has(a.type) && !CATEGORICALS.has(b.type)
61 | ) return sortByNullity(b, a);
62 | return sortByCardinality(a, b);
63 | }
64 |
65 | // SORT DFs
66 |
67 | function compareString(a, b, prop) {
68 | if (a[prop] < b[prop]) return -1;
69 | if (a[prop] > b[prop]) return 1;
70 |
71 | return 0;
72 | }
73 |
74 | function comparePinned(a, b) {
75 | return b.isPinned - a.isPinned;
76 | }
77 |
78 | export function sortDFArr(arr: IDFProfileWState[], by = 'dfName') {
79 | let compare;
80 | if (by === 'dfName') {
81 | compare = (a, b) => compareString(a, b, by);
82 | }
83 | if (by === 'lastUpdatedTime') {
84 | // invert so more recent at top
85 | compare = (a, b) => b[by] - a[by];
86 | }
87 |
88 | return arr.sort((a, b) => comparePinned(a, b) || compare(a, b));
89 | }
90 |
--------------------------------------------------------------------------------
/src/components/viz/BarAndLabel.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
22 |
23 |
42 |
--------------------------------------------------------------------------------
/src/components/viz/TopKSummary.svelte:
--------------------------------------------------------------------------------
1 |
51 |
52 |
53 |
62 | {#each topK.slice(0, 10) as { value, count }}
63 | {@const printValue = value === null ? ' null ∅' : value}
64 |
handleClick(e, value)}
67 | on:mouseover={() => handleColHover(value)}
68 | on:focus={() => handleColHover(value)}
69 | on:mouseleave={() => handleColHover(undefined)}
70 | on:blur={() => handleColHover(undefined)}
71 | >
72 |
73 |
82 |
83 | Export rows to code
86 |
87 |
90 | {printValue}
91 |
92 |
93 |
94 | {@const negligiblePercentage = count / totalRows < 0.0002}
95 | {@const percentage = negligiblePercentage
96 | ? '<.01%'
97 | : formatPercentage(count / totalRows)}
98 |
99 |
100 |
101 | {formatCount(count)}
102 |
103 | {#if percentage.length < 6} {/if}{#if percentage.length < 5} {/if} ({percentage})
107 |
108 |
109 |
110 | {/each}
111 |
112 | {#if cardinality > 10}
113 | {@const numExtra = cardinality - 10}
114 |
115 |
116 | + {formatCount(numExtra)} more unique value{numExtra > 1
117 | ? 's'
118 | : ''}
119 |
120 |
121 |
128 |
129 | Export unique values to code
132 |
133 |
134 | {/if}
135 |
136 |
137 |
--------------------------------------------------------------------------------
/src/components/viz/bivariate/Area.svelte:
--------------------------------------------------------------------------------
1 |
5 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/components/viz/bivariate/AxisX.svelte:
--------------------------------------------------------------------------------
1 |
5 |
71 |
72 |
73 | {#each tickVals as tick, i (tick)}
74 |
78 | {#if gridlines !== false}
79 |
80 | {/if}
81 | {#if tickMarks === true}
82 |
89 | {/if}
90 | {formatTick(tick)}
98 |
99 | {/each}
100 |
106 | {xLabel}
107 |
108 | {#if baseline === true}
109 |
116 | {/if}
117 |
118 |
119 |
150 |
--------------------------------------------------------------------------------
/src/components/viz/bivariate/AxisY.svelte:
--------------------------------------------------------------------------------
1 |
5 |
61 |
62 |
63 | {#each tickVals as tick (tick)}
64 |
70 | {#if gridlines !== false}
71 |
77 | {/if}
78 | {#if tickMarks === true}
79 |
86 | {/if}
87 | {formatTick(tick)}
94 |
95 |
101 | {tick}
102 |
103 |
104 | {/each}
105 |
111 | {yLabel}
112 |
113 |
114 |
115 |
140 |
--------------------------------------------------------------------------------
/src/components/viz/bivariate/Bar.svelte:
--------------------------------------------------------------------------------
1 |
5 |
12 |
13 |
14 | {#each $data as d, i}
15 |
24 | {/each}
25 |
26 |
--------------------------------------------------------------------------------
/src/components/viz/bivariate/BiHistogram.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
d.value)}
27 | xDomain={[0, null]}
28 | {data}
29 | >
30 |
35 |
36 |
37 |
38 |
44 |
--------------------------------------------------------------------------------
/src/components/viz/bivariate/BiLine.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
d.period)}
21 | {data}
22 | >
23 |
29 |
30 |
31 |
32 |
38 |
--------------------------------------------------------------------------------
/src/components/viz/bivariate/Column.svelte:
--------------------------------------------------------------------------------
1 |
5 |
32 |
33 |
34 | {#each $data as d, i}
35 | {@const colHeight = columnHeight(d)}
36 | {@const xGot = $xGet(d)}
37 | {@const xPos = Array.isArray(xGot) ? xGot[0] : xGot}
38 | {@const colWidth = $xScale.bandwidth
39 | ? $xScale.bandwidth()
40 | : columnWidth(d)}
41 | {@const yValue = $y(d)}
42 |
55 | {#if showLabels && yValue}
56 | {yValue}
61 | {/if}
62 | {/each}
63 |
64 |
65 |
70 |
--------------------------------------------------------------------------------
/src/components/viz/bivariate/Line.svelte:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
27 |
--------------------------------------------------------------------------------
/src/components/viz/histogram/NumericHistogram.svelte:
--------------------------------------------------------------------------------
1 |
36 |
37 | 300}
42 | bind:buffer
43 | {top}
44 | fillColor={NUMERIC_TOKENS.vizFillClass}
45 | hoverColor={NUMERIC_TOKENS.vizHoverClass}
46 | baselineStrokeColor={NUMERIC_TOKENS.vizStrokeClass}
47 | {data}
48 | {left}
49 | {right}
50 | width={effectiveWidth}
51 | height={height + 6 * (fontSize + buffer + anchorBuffer) + anchorBuffer}
52 | bottom={6 * (fontSize + buffer + anchorBuffer / 2)}
53 | >
54 |
55 |
56 |
62 |
63 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | {#each [['min', min], ['25%', qlow], ['median', median], ['mean', mean], ['75%', qhigh], ['max', max]] as [label, value], i}
78 | {@const yi =
79 | y(0) +
80 | anchorBuffer +
81 | i * (fontSize + buffer + anchorBuffer / 2) +
82 | anchorBuffer * 2}
83 |
84 |
92 |
99 | {/each}
100 |
101 |
102 | {#each [['min', min], ['25%', qlow], ['median', median], ['mean', mean], ['75%', qhigh], ['max', max]] as [label, value], i}
103 | {@const yi =
104 | y(0) +
105 | anchorBuffer +
106 | i * (fontSize + buffer + anchorBuffer / 2) +
107 | anchorBuffer * 2}
108 | {@const anchor = x(value) < width / 2 ? 'start' : 'end'}
109 | {@const anchorPlacement =
110 | anchor === 'start' ? anchorBuffer : -anchorBuffer}
111 |
112 |
130 | {/each}
131 |
132 |
133 |
134 |
--------------------------------------------------------------------------------
/src/components/viz/histogram/SmallHistogram.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
25 |
--------------------------------------------------------------------------------
/src/components/viz/histogram/SummaryStatLabel.svelte:
--------------------------------------------------------------------------------
1 |
66 |
67 |
68 | handleClick(e, label)}
71 | on:mouseenter={handleHover}
72 | on:mouseleave={handleUnhover}
73 | >
74 |
80 | {label}
81 |
82 |
83 |
88 |
89 |
90 | {formatDisplay(type, label, value)}
98 |
105 |
106 |
107 |
112 |
--------------------------------------------------------------------------------
/src/components/viz/histogram/TimestampHistogram.svelte:
--------------------------------------------------------------------------------
1 |
26 |
27 | {#if interval}
28 |
29 |
30 | This column spans {timeLength}.
31 |
32 |
33 | {/if}
34 | 300}
36 | fillColor={TIMESTAMP_TOKENS.vizFillClass}
37 | baselineStrokeColor={TIMESTAMP_TOKENS.vizStrokeClass}
38 | {data}
39 | left={0}
40 | right={0}
41 | width={effectiveWidth}
42 | {height}
43 | bottom={40}
44 | >
45 |
46 | {@const yStart = y.range()[0] + fontSize + buffer * 1.5}
47 | {@const yEnd = y.range()[0] + fontSize * 2 + buffer * 1.75}
48 | {@const xStart = x.range()[0]}
49 | {@const xEnd = x.range()[1]}
50 | {@const start = removeTimezoneOffset(new Date(x.domain()[0] * 1000))}
51 | {@const end = removeTimezoneOffset(new Date(x.domain()[1] * 1000))}
52 | {@const isSameDay =
53 | start.getFullYear() === end.getFullYear() &&
54 | start.getMonth() === end.getMonth() &&
55 | start.getDate() === end.getDate()}
56 | {@const emphasize = 'font-semibold'}
57 | {@const deEmphasize = 'fill-gray-500 italic'}
58 |
59 |
64 | {datePortion(start)}
65 |
66 | {#if type !== 'DATE'}
67 |
72 | {timePortion(start)}
73 |
74 | {/if}
75 |
76 |
77 |
83 | {datePortion(end)}
84 |
85 | {#if type !== 'DATE'}
86 |
92 | {timePortion(end)}
93 |
94 | {/if}
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/src/components/viz/histogram/histogram-tooltip/HistogramTooltip.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 | {#if !(value == undefined)}
16 |
17 |
24 | {leftBinInclusive ? '[' : '('}{formatFloat(value.low)}, {formatFloat(
25 | value.high
26 | )}{']'}: {formatInteger(value.count)}
27 | row{#if value.count !== 1}s{/if} (click bar to export)
28 |
29 |
30 | {/if}
31 |
--------------------------------------------------------------------------------
/src/components/viz/timestamp/TimestampBound.svelte:
--------------------------------------------------------------------------------
1 |
21 |
22 |
30 |
--------------------------------------------------------------------------------
/src/components/viz/timestamp/TimestampMouseoverAnnotation.svelte:
--------------------------------------------------------------------------------
1 |
34 |
35 |
36 |
43 | {#each [[yAccessor, 'rgb(100,100,100)']] as [accessor, color]}
44 | {@const cx = $X(point[xAccessor])}
45 | {@const cy = $Y(point[accessor])}
46 | {#if cx && cy}
47 |
48 | {/if}
49 | {/each}
50 |
56 |
62 | {standardTimestampFormat(xStartLabel)} - {standardTimestampFormat(
63 | xEndLabel
64 | )}
65 |
66 |
72 | {formatInteger(~~point[yAccessor])} row{#if point[yAccessor] !== 1}s{/if}
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/src/components/viz/timestamp/TimestampPaths.svelte:
--------------------------------------------------------------------------------
1 |
119 |
120 |
121 |
129 |
130 |
131 | $plotConfig.width * $plotConfig.devicePixelRatio
136 | ? 1
137 | : 0}
138 | >
139 |
146 |
153 |
154 |
--------------------------------------------------------------------------------
/src/components/viz/timestamp/TimestampProfileSummary.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 |
27 |
28 |
29 | {#if interval}
30 | Range is {intervalToTimestring(interval)}
31 | {/if}
32 |
33 |
34 | {#if zoomed}
35 | Zoomed to {numZoomedRows} rows ({formatBigNumberPercentage(
36 | numZoomedRows / numTotalRows
37 | )}).
38 | {/if}
39 |
40 |
41 |
42 |
Ctrl + Click + Drag to zoom
43 |
44 |
--------------------------------------------------------------------------------
/src/components/viz/timestamp/ZoomWindow.svelte:
--------------------------------------------------------------------------------
1 |
16 |
17 |
25 |
32 |
--------------------------------------------------------------------------------
/src/components/viz/timestamp/extremum-resolution-store.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @module extremum-resolution-store
3 | * This specialized store handles the resolution of plot bounds based
4 | * on multiple extrema. If multiple components set a maximum value within
5 | * the same namespace, the store will automatically pick the largest
6 | * for the store value. This enables us to determine, for instance, if
7 | * multiple lines are on the same chart, which ones determine the bounds.
8 | */
9 | import { max, min } from 'd3-array';
10 | import { cubicOut } from 'svelte/easing';
11 | import { tweened } from 'svelte/motion';
12 | import { derived, get, writable, type Writable } from 'svelte/store';
13 | import type { EasingFunction } from 'svelte/transition';
14 |
15 | const LINEAR_SCALE_STORE_DEFAULTS = {
16 | duration: 0,
17 | easing: cubicOut,
18 | direction: 'min',
19 | namespace: undefined,
20 | alwaysOverrideInitialValue: false
21 | };
22 |
23 | interface extremumArgs {
24 | duration?: number;
25 | easing?: EasingFunction;
26 | direction?: string;
27 | alwaysOverrideInitialValue?: boolean;
28 | }
29 |
30 | interface Extremum {
31 | value: number | Date;
32 | override?: boolean;
33 | }
34 |
35 | interface ExtremaStoreValue {
36 | [key: string]: Extremum;
37 | }
38 |
39 | const extremaFunctions = { min, max };
40 |
41 | export function createExtremumResolutionStore(
42 | initialValue: number | Date = undefined,
43 | passedArgs: extremumArgs = {}
44 | ) {
45 | const args = { ...LINEAR_SCALE_STORE_DEFAULTS, ...passedArgs };
46 | const storedValues: Writable = writable({});
47 | let tweenProps = {
48 | duration: args.duration,
49 | easing: args.easing
50 | };
51 | const valueTween = tweened(initialValue, tweenProps);
52 | function _update(key: string, value: number | Date, override = false) {
53 | // FIXME: there's an odd bug where if I don't check for equality first, I tend
54 | // to get an infinite loop with dates and the downstream scale.
55 | // This is easily fixed by only updating if the value has in fact changed.
56 | const extremum = get(storedValues)[key];
57 | if (extremum?.value === value && extremum?.override === override) {
58 | return;
59 | }
60 | storedValues.update(storeValue => {
61 | if (!(key in storeValue)) {
62 | storeValue[key] = { value: undefined, override: false };
63 | }
64 | storeValue[key].value = value;
65 | storeValue[key].override = override;
66 | return storeValue;
67 | });
68 | }
69 | /** add the initial value as its own key, if set by user. */
70 | if (initialValue && args.alwaysOverrideInitialValue === false) {
71 | _update('__initial_value__', initialValue);
72 | }
73 |
74 | function _remove(key: string) {
75 | storedValues.update(storeValue => {
76 | delete storeValue[key];
77 | return storeValue;
78 | });
79 | }
80 |
81 | const domainExtremum = derived(
82 | storedValues,
83 | $storedValues => {
84 | let extremum;
85 | const extrema: Extremum[] = [...Object.values($storedValues)];
86 | for (const entry of extrema) {
87 | if (entry.override) {
88 | extremum = entry.value;
89 | break;
90 | } else {
91 | extremum = extremaFunctions[args.direction]([
92 | entry.value,
93 | extremum
94 | ]);
95 | }
96 | }
97 | return extremum;
98 | },
99 | initialValue
100 | );
101 |
102 | // set the final tween with the value.
103 | domainExtremum.subscribe(value => {
104 | if (value !== undefined) {
105 | valueTween.set(value, tweenProps);
106 | }
107 | });
108 |
109 | const returnedStore = {
110 | subscribe: valueTween.subscribe,
111 | setWithKey(key, value = undefined, override = undefined) {
112 | _update(key, value, override);
113 | },
114 | removeKey(key: string) {
115 | _remove(key);
116 | },
117 | setTweenProps(tweenPropsArgs) {
118 | tweenProps = tweenPropsArgs;
119 | }
120 | };
121 | return returnedStore;
122 | }
123 |
--------------------------------------------------------------------------------
/src/components/viz/timestamp/utils.ts:
--------------------------------------------------------------------------------
1 | import { line, area, curveLinear, curveStep } from 'd3-shape';
2 | import type { ScaleLinear, ScaleTime } from 'd3-scale';
3 |
4 | export type GraphicScale =
5 | | ScaleLinear
6 | | ScaleTime;
7 |
8 | /**
9 | * Creates a string to be fed into the d attribute of a path,
10 | * producing a single path definition for one circle.
11 | * These completed, segmented arcs will not overlap in a way where
12 | * we can overplot if part of the same path.
13 | */
14 | export function circlePath(cx: number, cy: number, r: number): string {
15 | return `
16 | M ${cx - r}, ${cy}
17 | a ${r},${r} 0 1,0 ${r * 2},0
18 | a ${r},${r} 0 1,0 ${-r * 2},0
19 | `;
20 | }
21 |
22 | const curves = {
23 | curveLinear,
24 | curveStep
25 | };
26 |
27 | export function pathDoesNotDropToZero(yAccessor: string) {
28 | return (d, i: number, arr) => {
29 | return (
30 | !isNaN(d[yAccessor]) &&
31 | d[yAccessor] !== undefined &&
32 | // remove all zeroes where the previous or next value is also zero.
33 | // these do not add to our understanding.
34 | (!(i !== 0 && d[yAccessor] === 0 && arr[i - 1][yAccessor] === 0) ||
35 | !(
36 | i !== arr.length - 1 &&
37 | d[yAccessor] === 0 &&
38 | arr[i + 1][yAccessor] === 0
39 | ))
40 | );
41 | };
42 | }
43 |
44 | interface LineGeneratorArguments {
45 | xAccessor: string;
46 | xScale: ScaleLinear | ScaleTime;
47 | yScale: ScaleLinear | ScaleTime;
48 | curve: string;
49 | pathDefined?: (
50 | datum: object,
51 | i: number,
52 | arr: ArrayLike
53 | ) => boolean;
54 | }
55 |
56 | /**
57 | * A convenience function to generate a nice SVG path for a time series.
58 | * FIXME: rename to timeSeriesLineFactory.
59 | * FIXME: once we've gotten the data generics in place and threaded into components, let's make sure to type this.
60 | */
61 | export function lineFactory(args: LineGeneratorArguments) {
62 | return (yAccessor: string) =>
63 | line()
64 | .x(d => args.xScale(d[args.xAccessor]))
65 | .y(d => args.yScale(d[yAccessor]))
66 | .curve(curves[args.curve] || curveLinear)
67 | .defined(args.pathDefined || pathDoesNotDropToZero(yAccessor));
68 | }
69 |
70 | /**
71 | * A convenience function to generate a nice SVG area path for a time series.
72 | * FIXME: rename to timeSeriesAreaFactory.
73 | * FIXME: once we've gotten the data generics in place and threaded into components, let's make sure to type this.
74 | */
75 | export function areaFactory(args: LineGeneratorArguments) {
76 | return (yAccessor: string) =>
77 | area()
78 | .x(d => ~~args.xScale(d[args.xAccessor]))
79 | .y0(~~args.yScale(0))
80 | .y1(d => ~~args.yScale(d[yAccessor]))
81 | .curve(curves[args.curve] || curveLinear)
82 | .defined(args.pathDefined || pathDoesNotDropToZero(yAccessor));
83 | }
84 |
85 | /**
86 | * Return a list of ticks to be represented on the
87 | * axis or grid depending on axis-side, it's length and
88 | * the data type of field
89 | */
90 | export function getTicks(
91 | xOrY: string,
92 | scale: GraphicScale,
93 | axisLength: number,
94 | isDate: boolean
95 | ) {
96 | const tickCount = ~~(axisLength / (xOrY === 'x' ? 150 : 50));
97 | let ticks = scale.ticks(tickCount);
98 |
99 | if (ticks.length <= 1) {
100 | if (isDate) {
101 | ticks = scale.domain();
102 | } else {
103 | ticks = scale.nice().domain();
104 | }
105 | }
106 |
107 | return ticks;
108 | }
109 |
--------------------------------------------------------------------------------
/src/dataAPI/jupyter/cell.ts:
--------------------------------------------------------------------------------
1 | import type { ICellModel, ICodeCellModel } from '@jupyterlab/cells';
2 | import { Signal } from '@lumino/signaling';
3 |
4 | export default class CellAPI {
5 | _runSignal = new Signal(this);
6 | model: ICellModel;
7 | index: number;
8 |
9 | constructor(model: ICellModel, index: number) {
10 | this.model = model;
11 | this.index = index;
12 | }
13 |
14 | set text(text: string) {
15 | this.model.value.text = text;
16 | }
17 |
18 | get text() {
19 | return this.model.value.text;
20 | }
21 |
22 | get type() {
23 | return this.model.type;
24 | }
25 |
26 | // an event emitted when the code/markdown changes
27 | edited() {
28 | return this.model.value.changed;
29 | }
30 |
31 | // an event emitted when this cell is run
32 | run() {
33 | return this._runSignal;
34 | }
35 |
36 | getExecutionCount(): number | undefined {
37 |
38 | if (this.type === "code") {
39 | return (this.model as ICodeCellModel).executionCount
40 | }
41 |
42 | return undefined
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/dataAPI/jupyter/notebook.ts:
--------------------------------------------------------------------------------
1 | import { NotebookPanel, Notebook, NotebookActions } from '@jupyterlab/notebook';
2 | import { PromiseDelegate } from '@lumino/coreutils';
3 | import { PathExt } from '@jupyterlab/coreutils';
4 | import { Signal, type ISignal } from '@lumino/signaling';
5 | import _ from 'lodash';
6 |
7 | import CellAPI from './cell';
8 | // import KernelAPI from './kernel'
9 |
10 | export class NotebookAPI {
11 | private readonly _ready: PromiseDelegate;
12 | private _changed = new Signal(this);
13 | private _hasConnection = false;
14 |
15 | panel: NotebookPanel | undefined;
16 | // kernel: KernelAPI;
17 | cells: CellAPI[];
18 | mostRecentExecutionCount: number;
19 |
20 | constructor(notebookPanel: NotebookPanel) {
21 | this.panel = notebookPanel;
22 | this._ready = new PromiseDelegate();
23 |
24 | if (!(this.panel == undefined)) {
25 | this.listenToSession();
26 | this.panel.revealed.then(() => {
27 | this.listenToCells();
28 | this._hasConnection = true
29 | this._ready.resolve();
30 | });
31 | } else {
32 | this._ready.resolve();
33 | }
34 | }
35 |
36 | saveToNotebookMetadata(key: string, value: any) {
37 | this.panel.model.metadata.set(key, value);
38 | }
39 |
40 | // ready is a Promise that resolves once the notebook is done loading
41 | get ready(): Promise {
42 | return this._ready.promise;
43 | }
44 |
45 | get hasConnection(): boolean {
46 | return this._hasConnection;
47 | }
48 |
49 | // changed is a signal emitted when various things in this notebook change
50 | get changed(): ISignal {
51 | return this._changed;
52 | }
53 |
54 | get notebook(): Notebook | undefined {
55 | return this.panel?.content;
56 | }
57 |
58 | get language(): string | undefined {
59 | const meta = this.notebook?.model?.metadata;
60 | if (meta?.has('language_info')) {
61 | const val = meta?.get('language_info')?.valueOf();
62 | if (val instanceof Object) {
63 | return val['name'];
64 | }
65 | }
66 | }
67 |
68 | get path(): string | undefined {
69 | return this.panel?.sessionContext?.path;
70 | }
71 |
72 | get name(): string | undefined {
73 | return this.path ? PathExt.basename(this.path) : undefined;
74 | }
75 |
76 | get activeCell(): CellAPI | undefined {
77 | if (this.notebook && this.cells) {
78 | const active = this.notebook.activeCell;
79 | return this.cells.find(c => c.model.id === active.model.id);
80 | }
81 | return undefined;
82 | }
83 |
84 | public addCell(kind: 'code' | 'markdown', text: string, index?: number) {
85 | let cell;
86 | if (kind === 'code') {
87 | cell = this.notebook.model.contentFactory.createCodeCell({});
88 | } else {
89 | cell = this.notebook.model.contentFactory.createMarkdownCell({});
90 | }
91 | cell.value.text = text;
92 |
93 | // if no index provided, insert at current position
94 | if (index == undefined) {
95 | const active = this.notebook.activeCell;
96 | index = this.cells.findIndex(c => c.model.id === active.model.id);
97 | index += 1
98 | }
99 |
100 | this.notebook.model.cells.insert(index, cell);
101 | }
102 |
103 | // Various notebook level events you can listen to
104 | private listenToCells() {
105 | this.loadCells();
106 |
107 | // event fires when cells are added, deleted, or moved
108 | this.notebook.model.cells.changed.connect(() => {
109 | this.loadCells();
110 | this._changed.emit('cells');
111 | });
112 |
113 | // event fires when the user selects a cell
114 | this.notebook.activeCellChanged.connect((_, cell) => {
115 | this._changed.emit('activeCell');
116 | });
117 |
118 | // event fires when any cell is run
119 | NotebookActions.executed.connect((_, args) => {
120 | // can get execution signals from other notebooks
121 | if (args.notebook.id === this.notebook.id) {
122 | const cell = this.cells.find(
123 | c => c.model.id === args.cell.model.id
124 | );
125 | if (cell) {
126 | const exCount = cell.getExecutionCount()
127 | if (exCount) {
128 | this.mostRecentExecutionCount = exCount
129 | }
130 | cell._runSignal.emit();
131 | this._changed.emit('cell run');
132 | }
133 | }
134 | });
135 |
136 | this.notebook.model.metadata.changed.connect((_, args) => {
137 | this._changed.emit('language changed');
138 | })
139 |
140 | }
141 |
142 | private listenToSession() {
143 | // event fires when the user moves or renames their notebook
144 | this.panel.sessionContext.propertyChanged.connect((_, prop) => {
145 | if (prop === 'path') {
146 | this._changed.emit('path');
147 | }
148 | if (prop === 'name') {
149 | this._changed.emit('name');
150 | }
151 | });
152 | }
153 |
154 | private loadCells() {
155 | this.cells = [];
156 | for (let i = 0; i < this.notebook.model.cells.length; i++) {
157 | this.cells.push(new CellAPI(this.notebook.model.cells.get(i), i));
158 | }
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | JupyterFrontEnd,
3 | JupyterFrontEndPlugin
4 | } from '@jupyterlab/application';
5 | import { INotebookTracker } from '@jupyterlab/notebook';
6 | import { NotebookAPI } from './dataAPI/jupyter/notebook';
7 | import { ProfilePanel } from './ProfilePanel';
8 |
9 | /**
10 | * Initialization data for the AutoProfiler extension.
11 | */
12 | const plugin: JupyterFrontEndPlugin = {
13 | id: 'AutoProfiler:plugin',
14 | autoStart: true,
15 | activate: (app: JupyterFrontEnd, notebookTracker: INotebookTracker) => {
16 | const panel: ProfilePanel = new ProfilePanel();
17 | app.shell.add(panel, 'right', { rank: 1000 });
18 |
19 | // emitted when the user's notebook changes, null if all notebooks close
20 | notebookTracker.currentChanged.connect((_, widget) => {
21 | const notebook = new NotebookAPI(widget);
22 | notebook.ready.then(async () => {
23 | // connect panel to notebook
24 | await panel.connectNotebook(notebook);
25 | });
26 | });
27 | },
28 | requires: [INotebookTracker]
29 | };
30 |
31 | export default plugin;
32 |
--------------------------------------------------------------------------------
/src/stores.ts:
--------------------------------------------------------------------------------
1 | import { type Writable, writable } from 'svelte/store';
2 |
3 | // UI stores
4 | export const currentHoveredCol: Writable = writable(undefined);
5 | export const showIndex: Writable = writable(false);
6 |
--------------------------------------------------------------------------------
/src/svg.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.svg' {
2 | const value: string;
3 | export default value;
4 | }
5 |
--------------------------------------------------------------------------------
/style/base.css:
--------------------------------------------------------------------------------
1 | /*
2 | See the JupyterLab Developer Guide for useful CSS Patterns:
3 |
4 | https://jupyterlab.readthedocs.io/en/stable/developer/css.html
5 | */
6 | .auto-profile-app {
7 | /* css for the main side panel view container */
8 | background: var(--jp-layout-color1);
9 | color: var(--jp-ui-font-color1);
10 |
11 | /* This is needed so that all font sizing of children done in ems is
12 | * relative to this base size */
13 | font-size: var(--jp-ui-font-size1);
14 |
15 | /* text-align: center; */
16 | overflow-y: auto;
17 | min-width: 400px;
18 | width: 450px;
19 | }
20 |
21 | .auto-profile-wrapper {
22 | min-width: 400px;
23 | }
24 |
25 | #header-icon {
26 | background: url('./logo.svg');
27 | min-width: 100%;
28 | min-height: 6em;
29 | background-size: contain;
30 | display: inline-block;
31 | background-repeat: no-repeat;
32 | background-position: center;
33 | margin-top: 1em;
34 | }
35 |
36 | .autoprofile-logo {
37 | align-items: center;
38 | display: flex;
39 | }
--------------------------------------------------------------------------------
/style/index.css:
--------------------------------------------------------------------------------
1 | @import url('base.css');
2 |
--------------------------------------------------------------------------------
/style/index.js:
--------------------------------------------------------------------------------
1 | import './base.css';
2 |
--------------------------------------------------------------------------------
/style/logo.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'jit',
3 | content: [
4 | './src/**/*.{html,js,svelte,ts}',
5 | ],
6 | theme: {
7 | extend: {},
8 | },
9 | plugins: [],
10 | variants: {
11 | opacity: ({ after }) => after(["disabled"]),
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/svelte/tsconfig.json",
3 | "compilerOptions": {
4 | "allowSyntheticDefaultImports": true,
5 | "composite": true,
6 | "declaration": true,
7 | "esModuleInterop": true,
8 | "incremental": true,
9 | "jsx": "react",
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "noEmitOnError": true,
13 | "noImplicitAny": false,
14 | "noUnusedLocals": true,
15 | "preserveWatchOutput": true,
16 | "resolveJsonModule": true,
17 | "outDir": "lib",
18 | "rootDir": "src",
19 | "strict": false,
20 | "strictNullChecks": false,
21 | "target": "es2017",
22 | "types": [
23 | "svelte",
24 | "node"
25 | ],
26 | "isolatedModules": true,
27 | "forceConsistentCasingInFileNames": true
28 | },
29 | "include": [
30 | "src/**/*",
31 | "style"
32 | ]
33 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | // const version = require('./package.json').version;
3 | const SveltePreprocess = require('svelte-preprocess');
4 |
5 | // Custom webpack rules
6 | const rules = [
7 | { test: /\.ts$/, loader: 'ts-loader' },
8 | {
9 | test: /\.js$/,
10 | enforce: 'pre',
11 | loader: 'source-map-loader'
12 | },
13 | { test: /\.css$/, use: ['style-loader', 'css-loader'] },
14 | {
15 | test: /\.svelte$/,
16 | loader: 'svelte-loader',
17 | options: {
18 | preprocess: SveltePreprocess({
19 | postcss: {
20 | plugins: [require('tailwindcss'), require('autoprefixer')]
21 | }
22 | })
23 | }
24 | },
25 | {
26 | test: /\.svg$/,
27 | loader: 'svg-url-loader'
28 | }
29 | ];
30 |
31 | // Packages that shouldn't be bundled but loaded at runtime
32 | const externals = [
33 | '@jupyter-widgets/base',
34 | '@jupyterlab/notebook',
35 | '@lumino/widgets'
36 | ];
37 |
38 | const resolve = {
39 | extensions: [
40 | '.webpack.js',
41 | '.web.js',
42 | '.ts',
43 | '.js',
44 | '.svelte',
45 | '.css',
46 | '.svg'
47 | ],
48 | mainFields: ['svelte', 'browser', 'module', 'main']
49 | };
50 |
51 | module.exports = [
52 | /**
53 | * Lab extension
54 | *
55 | * This builds the lib/ folder with the JupyterLab extension.
56 | */
57 | {
58 | entry: './src/index.ts',
59 | output: {
60 | filename: 'index.js',
61 | path: path.resolve(__dirname, 'lib'),
62 | libraryTarget: 'amd',
63 | publicPath: ''
64 | },
65 | module: {
66 | rules: rules
67 | },
68 | externals,
69 | resolve,
70 | ignoreWarnings: [/Failed to parse source map/]
71 | }
72 | ];
73 |
--------------------------------------------------------------------------------