Showing preview only (790K chars total). Download the full file or copy to clipboard to get everything.
Repository: Kanaries/pygwalker
Branch: main
Commit: c6e0fb76dad4
Files: 180
Total size: 742.4 KB
Directory structure:
gitextract__wqcb176/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── any-questions-topics-you-want-to-share.md
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows/
│ ├── auto-ci.yml
│ └── publish.yml
├── .gitignore
├── .gitmodules
├── .pylintrc
├── CITATION.cff
├── CONDUCT.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── app/
│ ├── .gitignore
│ ├── components.json
│ ├── index.html
│ ├── package.json
│ ├── postcss.config.js
│ ├── src/
│ │ ├── components/
│ │ │ ├── codeExportModal/
│ │ │ │ ├── index.tsx
│ │ │ │ └── usePythonCode.ts
│ │ │ ├── initModal/
│ │ │ │ └── index.tsx
│ │ │ ├── options.tsx
│ │ │ ├── preview/
│ │ │ │ └── index.tsx
│ │ │ ├── runcellBanner/
│ │ │ │ └── index.tsx
│ │ │ ├── ui/
│ │ │ │ ├── badge.tsx
│ │ │ │ ├── button.tsx
│ │ │ │ ├── checkbox.tsx
│ │ │ │ ├── dialog.tsx
│ │ │ │ ├── input.tsx
│ │ │ │ ├── label.tsx
│ │ │ │ ├── select.tsx
│ │ │ │ ├── tabs.tsx
│ │ │ │ ├── toggle-group.tsx
│ │ │ │ └── toggle.tsx
│ │ │ ├── uploadChartModal/
│ │ │ │ └── index.tsx
│ │ │ └── uploadSpecModal/
│ │ │ └── index.tsx
│ │ ├── dataSource/
│ │ │ └── index.tsx
│ │ ├── index.css
│ │ ├── index.tsx
│ │ ├── interfaces/
│ │ │ └── index.ts
│ │ ├── lib/
│ │ │ ├── dslToWorkflow.ts
│ │ │ ├── utils.ts
│ │ │ └── vegaToDsl.ts
│ │ ├── notify/
│ │ │ └── index.tsx
│ │ ├── store/
│ │ │ ├── common.ts
│ │ │ ├── communication.ts
│ │ │ └── context.ts
│ │ ├── tools/
│ │ │ ├── exportDataframe.tsx
│ │ │ ├── exportTool.tsx
│ │ │ ├── openDesktop.tsx
│ │ │ ├── runcellTool.tsx
│ │ │ └── saveTool.tsx
│ │ └── utils/
│ │ ├── communication.tsx
│ │ ├── context.tsx
│ │ ├── formatSpec.ts
│ │ ├── save.ts
│ │ ├── theme.ts
│ │ ├── tracker.ts
│ │ └── userConfig.ts
│ ├── tailwind.config.js
│ ├── tsconfig.json
│ └── vite.config.ts
├── bin/
│ └── pygwalker_command.py
├── docs/
│ ├── CONTRIBUTING.md
│ ├── DEVELOPMENT.md
│ ├── README.de.md
│ ├── README.es.md
│ ├── README.fr.md
│ ├── README.ja.md
│ ├── README.ko.md
│ ├── README.ru.md
│ ├── README.tr.md
│ └── README.zh.md
├── environment.yml
├── examples/
│ ├── README.md
│ ├── component_demo.ipynb
│ ├── dash_demo.py
│ ├── gradio_demo.py
│ ├── gw_config.json
│ ├── html_demo.py
│ ├── jupyter_demo.ipynb
│ ├── marimo_demo.py
│ ├── reflex_demo/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── __init__.py
│ │ ├── app/
│ │ │ ├── __init__.py
│ │ │ └── app.py
│ │ └── rxconfig.py
│ ├── streamlit_demo.py
│ └── web_server_demo.py
├── pygwalker/
│ ├── __init__.py
│ ├── _constants.py
│ ├── _typing.py
│ ├── api/
│ │ ├── __init__.py
│ │ ├── adapter.py
│ │ ├── anywidget.py
│ │ ├── component.py
│ │ ├── gradio.py
│ │ ├── html.py
│ │ ├── jupyter.py
│ │ ├── kanaries_cloud.py
│ │ ├── marimo.py
│ │ ├── pygwalker.py
│ │ ├── reflex.py
│ │ ├── streamlit.py
│ │ └── webserver.py
│ ├── communications/
│ │ ├── __init__.py
│ │ ├── anywidget_comm.py
│ │ ├── base.py
│ │ ├── gradio_comm.py
│ │ ├── hacker_comm.py
│ │ ├── reflex_comm.py
│ │ └── streamlit_comm.py
│ ├── data_parsers/
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── cloud_dataset_parser.py
│ │ ├── database_parser.py
│ │ ├── modin_parser.py
│ │ ├── pandas_parser.py
│ │ ├── polars_parser.py
│ │ └── spark_parser.py
│ ├── errors.py
│ ├── services/
│ │ ├── __init__.py
│ │ ├── check_update.py
│ │ ├── cloud_service.py
│ │ ├── config.py
│ │ ├── data_parsers.py
│ │ ├── fname_encodings.py
│ │ ├── format_invoke_walk_code.py
│ │ ├── global_var.py
│ │ ├── kaggle.py
│ │ ├── kanaries_cli_login.py
│ │ ├── preview_image.py
│ │ ├── render.py
│ │ ├── spec.py
│ │ ├── streamlit_components.py
│ │ ├── tip_tools.py
│ │ ├── track.py
│ │ └── upload_data.py
│ ├── templates/
│ │ ├── .gitignore
│ │ ├── index.html
│ │ ├── jupyter_iframe_message.html
│ │ ├── pygwalker_iframe.html
│ │ └── pygwalker_main_page.html
│ └── utils/
│ ├── __init__.py
│ ├── check_walker_params.py
│ ├── custom_sqlglot.py
│ ├── display.py
│ ├── dsl_transform.py
│ ├── encode.py
│ ├── estimate_tools.py
│ ├── execute_env_check.py
│ ├── free_port.py
│ ├── log.py
│ ├── payload_to_sql.py
│ ├── randoms.py
│ └── runtime_env.py
├── pygwalker_tools/
│ ├── __init__.py
│ └── metrics/
│ ├── __init__.py
│ ├── api.py
│ └── core.py
├── pyproject.toml
├── scripts/
│ ├── __init__.py
│ ├── ci_run_pytest.py
│ ├── compile.sh
│ ├── test-init.py
│ └── test-init.sh
├── tests/
│ ├── .gitignore
│ ├── __init__.py
│ ├── field-spec.ipynb
│ ├── main-modin.ipynb
│ ├── main-polars.ipynb
│ ├── main.ipynb
│ ├── test_component_api.ipynb
│ ├── test_data_parsers.py
│ ├── test_dsl_transform.py
│ ├── test_fname_encodings.py
│ └── test_format_invoke_walk_code.py
└── tutorials/
├── README.md
└── pygwalker_complete_tutorial.ipynb
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/any-questions-topics-you-want-to-share.md
================================================
---
name: Any Questions/Topics you want to share
about: Describe anything you want to talk about related to pygwalker
title: ''
labels: ''
assignees: ''
---
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: "[BUG] pygwalker bug report"
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Versions**
- pygwalker version:
- python version
- browser
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/workflows/auto-ci.yml
================================================
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
name: Auto CI
on:
push:
branches: [ "main", "dev" ]
pull_request:
branches: [ "main" ]
workflow_dispatch:
jobs:
build-js:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'yarn'
cache-dependency-path: ./app/yarn.lock
- name: Try Compiling
working-directory: ./
run: |
chmod u+x ./scripts/compile.sh
./scripts/compile.sh
- name: Uploading dist
uses: actions/upload-artifact@v4
with:
name: pygwalker-app
path: ./pygwalker/templates/dist/*
build-py:
needs: build-js
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
python-version: ['3.12', '3.13']
os-version: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os-version }}
steps:
- uses: actions/checkout@v4
- name: Download PyGWalkerApp
uses: actions/download-artifact@v4
id: build-py
with:
name: pygwalker-app
path: ./pygwalker/templates/dist
- name: Use Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Download test data
working-directory: ./
run: |
chmod u+x ./scripts/test-init.py
python ./scripts/test-init.py
- name: Try Installing
working-directory: ./
env:
PIP_ONLY_BINARY: "duckdb"
run: |
pip install ".[export]"
- name: Try Install Modin In Linux Os
if: ${{ matrix.os-version == 'ubuntu-latest' && matrix.python-version == '3.11' }}
run: |
pip install aiohttp==3.8.6
pip install "modin==0.23.1" "modin[ray]==0.23.1"
pip install pydantic==1.10.9
- name: Try Running
working-directory: ./tests/
run: |
pip install ipykernel nbconvert pandas polars
python -m ipykernel install --name python --user
jupyter kernelspec list
jupyter nbconvert --execute --ExecutePreprocessor.kernel_name=python --to html *.ipynb
- name: Try Pytest
timeout-minutes: 20
working-directory: ./
run: |
pip install duckdb_engine
pip install pytest
python ./scripts/ci_run_pytest.py
- name: Uploading notebooks
uses: actions/upload-artifact@v4
if: ${{ matrix.python-version == '3.12' && matrix.os-version == 'ubuntu-latest' }}
with:
name: notebook
path: |
./tests/main.html
./tests/offline.html
./tests/stress-test.html
================================================
FILE: .github/workflows/publish.yml
================================================
name: Publish PyPI
on:
workflow_dispatch:
jobs:
build-js:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'yarn'
cache-dependency-path: ./app/yarn.lock
- name: Try Compiling
working-directory: ./
run: |
chmod u+x ./scripts/compile.sh
./scripts/compile.sh
- name: Uploading dist
if: ${{ matrix.node-version == '22.x' }}
uses: actions/upload-artifact@v4
with:
name: pygwalker-app
path: ./pygwalker/templates/dist/*
build-py:
needs: [build-js]
strategy:
fail-fast: false
matrix:
python-version: ['3.12']
os-version: [ubuntu-latest]
runs-on: ${{ matrix.os-version }}
steps:
- uses: actions/checkout@v4
- name: Download PyGWalkerApp
uses: actions/download-artifact@v4
with:
name: pygwalker-app
path: ./pygwalker/templates/dist
- name: Use Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Try building
working-directory: ./
run: |
pip install build twine
python -m build .
- name: Uploading packages
if: ${{ matrix.python-version == '3.12' && matrix.os-version == 'ubuntu-latest' }}
uses: actions/upload-artifact@v4
with:
name: pygwalker
path: ./dist/*
- name: Uploading PyPI
if: ${{ matrix.python-version == '3.12' && matrix.os-version == 'ubuntu-latest' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags')) }}
uses: pypa/gh-action-pypi-publish@v1.12.4
with:
# PyPI user
# Password for your PyPI user or an access token
password: ${{ secrets.PYPI_TOKEN }}
# The target directory for distribution
packages-dir: dist
# Check metadata before uploading
verify-metadata: true
# Do not fail if a Python package distribution exists in the target package index
skip-existing: true
# Show verbose output.
verbose: true
# Show hash values of files to be uploaded
print-hash: true
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
__pycache__
dist
!/pygwalker/templates/dist/*
.DS_Store
venv
.ipynb_checkpoints
node_modules
poetry.lock
pygwalker/templates/graphic-walker.umd.js
pygwalker/templates/graphic-walker.iife.js
tests/*.csv
__pycache__/
.states
*.db
.web
*.py[cod]
assets/external/
================================================
FILE: .gitmodules
================================================
================================================
FILE: .pylintrc
================================================
# This Pylint rcfile contains a best-effort configuration to uphold the
# best-practices and style described in the Google Python style guide:
# https://google.github.io/styleguide/pyguide.html
#
# Its canonical open-source location is:
# https://google.github.io/styleguide/pylintrc
[MASTER]
# Files or directories to be skipped. They should be base names, not paths.
ignore=third_party
# Files or directories matching the regex patterns are skipped. The regex
# matches against base names, not paths.
ignore-patterns=
# Pickle collected data for later comparisons.
persistent=no
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
# Use multiple processes to speed up Pylint.
jobs=4
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
confidence=
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
#enable=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=abstract-method,
apply-builtin,
arguments-differ,
attribute-defined-outside-init,
backtick,
bad-option-value,
basestring-builtin,
buffer-builtin,
c-extension-no-member,
consider-using-enumerate,
cmp-builtin,
cmp-method,
coerce-builtin,
coerce-method,
delslice-method,
div-method,
duplicate-code,
eq-without-hash,
execfile-builtin,
file-builtin,
filter-builtin-not-iterating,
fixme,
getslice-method,
global-statement,
hex-method,
idiv-method,
implicit-str-concat,
import-error,
import-self,
import-star-module-level,
inconsistent-return-statements,
input-builtin,
intern-builtin,
invalid-str-codec,
locally-disabled,
long-builtin,
long-suffix,
map-builtin-not-iterating,
misplaced-comparison-constant,
missing-function-docstring,
missing-module-docstring,
metaclass-assignment,
next-method-called,
next-method-defined,
no-absolute-import,
no-else-break,
no-else-continue,
no-else-raise,
no-else-return,
no-init, # added
no-member,
no-name-in-module,
no-self-use,
nonzero-method,
oct-method,
old-division,
old-ne-operator,
old-octal-literal,
old-raise-syntax,
parameter-unpacking,
print-statement,
raising-string,
range-builtin-not-iterating,
raw_input-builtin,
rdiv-method,
reduce-builtin,
relative-import,
reload-builtin,
round-builtin,
setslice-method,
signature-differs,
standarderror-builtin,
suppressed-message,
sys-max-int,
too-few-public-methods,
too-many-ancestors,
too-many-arguments,
too-many-boolean-expressions,
too-many-branches,
too-many-instance-attributes,
too-many-locals,
too-many-nested-blocks,
too-many-public-methods,
too-many-return-statements,
too-many-statements,
trailing-newlines,
unichr-builtin,
unicode-builtin,
unnecessary-pass,
unpacking-in-except,
useless-else-on-loop,
useless-object-inheritance,
useless-suppression,
using-cmp-argument,
wrong-import-order,
xrange-builtin,
zip-builtin-not-iterating,
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
output-format=text
# Tells whether to display a full report or only the messages
reports=no
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details
#msg-template=
[BASIC]
# Good variable names which should always be accepted, separated by a comma
good-names=main,_
# Bad variable names which should always be refused, separated by a comma
bad-names=
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Include a hint for the correct naming format with invalid-name
include-naming-hint=no
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl
# Regular expression matching correct function names
function-rgx=^(?:(?P<exempt>setUp|tearDown|setUpModule|tearDownModule)|(?P<camel_case>_?[A-Z][a-zA-Z0-9]*)|(?P<snake_case>_?[a-z][a-z0-9_]*))$
# Regular expression matching correct variable names
variable-rgx=^[a-z][a-z0-9_]*$
# Regular expression matching correct constant names
const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
# Regular expression matching correct attribute names
attr-rgx=^_{0,2}[a-z][a-z0-9_]*$
# Regular expression matching correct argument names
argument-rgx=^[a-z][a-z0-9_]*$
# Regular expression matching correct class attribute names
class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
# Regular expression matching correct inline iteration names
inlinevar-rgx=^[a-z][a-z0-9_]*$
# Regular expression matching correct class names
class-rgx=^_?[A-Z][a-zA-Z0-9]*$
# Regular expression matching correct module names
module-rgx=^(_?[a-z][a-z0-9_]*|__init__)$
# Regular expression matching correct method names
method-rgx=(?x)^(?:(?P<exempt>_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P<camel_case>_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P<snake_case>_{0,2}[a-z][a-z0-9_]*))$
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=10
[TYPECHECK]
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=180
# TODO(https://github.com/PyCQA/pylint/issues/3352): Direct pylint to exempt
# lines made too long by directives to pytype.
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=(?x)(
^\s*(\#\ )?<?https?://\S+>?$|
^\s*(from\s+\S+\s+)?import\s+.+$)
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=yes
# Maximum number of lines in a module
max-module-lines=99999
# String used as indentation unit. The internal Google style guide mandates 2
# spaces. Google's externaly-published style guide says 4, consistent with
# PEP 8. Here, we use 2 spaces, for conformity with many open-sourced Google
# projects (like TensorFlow).
indent-string=' '
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=TODO
[STRING]
# This flag controls whether inconsistent-quotes generates a warning when the
# character used as a quote delimiter is used inconsistently within a module.
check-quote-consistency=no
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the name of dummy variables (i.e. expectedly
# not used).
dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_)
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,_cb
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools
[LOGGING]
# Logging modules to check that the string format arguments are in logging
# function parameter format
logging-modules=logging,absl.logging,tensorflow.io.logging
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
[SPELLING]
# Spelling dictionary name. Available dictionaries: none. To make it working
# install python-enchant package.
spelling-dict=
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to indicated private dictionary in
# --spelling-private-dict-file option instead of raising a message.
spelling-store-unknown-words=no
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,
TERMIOS,
Bastion,
rexec,
sets
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant, absl
# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no
[CLASSES]
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,
__new__,
setUp
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,
_fields,
_replace,
_source,
_make
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls,
class_
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=builtins.StandardError,
builtins.Exception,
builtins.BaseException
================================================
FILE: CITATION.cff
================================================
# This CITATION.cff file was generated with cffinit.
# Visit https://bit.ly/cffinit to generate yours today!
cff-version: 1.2.0
title: PyGWalker
message: >-
If you use this software, please cite it using the
metadata from this file.
type: software
authors:
- website: 'https://kanaries.net/'
name: Kanaries Open Source Community
repository-code: 'https://github.com/Kanaries/pygwalker'
url: 'https://kanaries.net/pygwalker'
abstract: >-
PyGWalker is a Python Library for Exploratory Data
Analysis with Visualization that can simplify your Jupyter
Notebook data analysis and data visualization workflow, by
turning your pandas dataframe into an interactive user
interface for visual exploration.
keywords:
- Data Analysis
- Exploratory Data Analysis
- Data Visualization tools
- Python Library
- interactive
license: Apache-2.0
================================================
FILE: CONDUCT.md
================================================
# Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others’ private information, such as a physical or email address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [support@kanaries.net](mailto:support@kanaries.net). All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
#### 1. Correction
Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
#### 2. Warning
Community Impact: A violation through a single incident or series of actions.
Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
#### 3. Temporary Ban
Community Impact: A serious violation of community standards, including sustained inappropriate behavior.
Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
#### 4. Permanent Ban
Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
Consequence: A permanent ban from any sort of public interaction within the community.
## Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2023] [Kanaries Data, Inc.]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: MANIFEST.in
================================================
include LICENSE
include README.md
graft pygwalker/templates/**
================================================
FILE: README.md
================================================
[English](README.md) | [Español](./docs/README.es.md) | [Français](./docs/README.fr.md) | [Deutsch](./docs/README.de.md) | [中文](./docs/README.zh.md) | [Türkçe](./docs/README.tr.md) | [日本語](./docs/README.ja.md) | [한국어](./docs/README.ko.md) | [Русский](./docs/README.ru.md)
<p align="center"><a href="https://github.com/Kanaries/pygwalker"><img width=100% alt="" src="https://github.com/user-attachments/assets/f90db669-6e5a-45d3-942e-547c9d0471c9" /></a></p>
<h2 align="center">PyGWalker: A Python Library for Exploratory Data Analysis with Visualization</h2>
<p align="center">
<a href="https://arxiv.org/abs/2406.11637">
<img src="https://img.shields.io/badge/arXiv-2406.11637-b31b1b.svg" height="18" align="center">
</a>
<a href="https://badge.fury.io/py/pygwalker">
<img src="https://badge.fury.io/py/pygwalker.svg" alt="PyPI version" height="18" align="center" />
</a>
<a href="https://mybinder.org/v2/gh/Kanaries/pygwalker/main">
<img src="https://mybinder.org/badge_logo.svg" alt="binder" height="18" align="center" />
</a>
<a href="https://pypi.org/project/pygwalker">
<img src="https://img.shields.io/pypi/dm/pygwalker" alt="PyPI downloads" height="18" align="center" />
</a>
<a href="https://anaconda.org/conda-forge/pygwalker"> <img src="https://anaconda.org/conda-forge/pygwalker/badges/version.svg" alt="conda-forge" height="18" align="center" /> </a>
</p>
<p align="center">
<a href="https://discord.gg/Z4ngFWXz2U">
<img alt="discord invitation link" src="https://dcbadge.vercel.app/api/server/Z4ngFWXz2U?style=flat" align="center" />
</a>
<a href='https://twitter.com/intent/follow?original_referer=https%3A%2F%2Fpublish.twitter.com%2F&ref_src=twsrc%5Etfw&screen_name=kanaries_data&tw_p=followbutton'>
<img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/kanaries_data?style=social" alt='Twitter' align="center" />
</a>
<a href="https://kanaries-community.slack.com/join/shared_invite/zt-20kpp56wl-ke9S0MxTcNQjUhKf6SOfvQ#/shared-invite/email">
<img src="https://img.shields.io/badge/Slack-green?style=flat-square&logo=slack&logoColor=white" alt="Join Kanaries on Slack" align="center" />
</a>
</p>
[**PyGWalker**](https://github.com/Kanaries/pygwalker) can simplify your Jupyter Notebook data analysis and data visualization workflow, by turning your pandas dataframe into an interactive user interface for visual exploration.
**PyGWalker** (pronounced like "Pig Walker", just for fun) is named as an abbreviation of "**Py**thon binding of **G**raphic **Walker**". It integrates Jupyter Notebook with [Graphic Walker](https://github.com/Kanaries/graphic-walker), an open-source alternative to Tableau. It allows data scientists to visualize / clean / annotates the data with simple drag-and-drop operations and even natural language queries.
https://github.com/Kanaries/pygwalker/assets/22167673/2b940e11-cf8b-4cde-b7f6-190fb10ee44b
> [!TIP]
> If you want more AI features, we also build [runcell](https://runcell.dev), an AI Code Agent in Jupyter that understands your code/data/cells and generate code, execute cells and take actions for you. It can be used in jupyter lab with `pip install runcell`
https://github.com/user-attachments/assets/9ec64252-864d-4bd1-8755-83f9b0396d38
Visit [Google Colab](https://colab.research.google.com/drive/171QUQeq-uTLgSj1u-P9DQig7Md1kpXQ2?usp=sharing), [Kaggle Code](https://www.kaggle.com/code/lxy21495892/airbnb-eda-pygwalker-demo) or [Graphic Walker Online Demo](https://graphic-walker.kanaries.net/) to test it out!
> If you prefer using R, check [GWalkR](https://github.com/Kanaries/GWalkR), the R wrapper of Graphic Walker.
> If you prefer a Desktop App that can be used offline and without any coding, check out [PyGWalker Desktop](https://kanaries.net/download?utm_source=pygwalker_github&utm_content=tip).
# Features
PyGWalker is a Python library that simplifies data analysis and visualization workflows by turning pandas DataFrames into interactive visual interfaces.
It offers a variety of features that make it a powerful tool for data exploration:
- ##### Interactive Data Exploration:
- Drag-and-drop interface for easy visualization creation.
- Real-time updates as you make changes to the visualization.
- Ability to zoom, pan, and filter the data.
- ##### Data Cleaning and Transformation:
- Visual data cleaning tools to identify and remove outliers or inconsistencies.
- Ability to create new variables and features based on existing data.
- ##### Advanced Visualization Capabilities:
- Support for various chart types (bar charts, line charts, scatter plots, etc.).
- Customization options for colors, labels, and other visual elements.
- Interactive features like tooltips and drill-down capabilities.
- ##### Integration with Jupyter Notebooks:
- Seamless integration with Jupyter Notebooks for a smooth workflow.
- ##### Open-Source and Free:
- Available for free and allows for customization and extension.
# Getting Started
> Check our video tutorial about using pygwalker, pygwalker + streamlit and pygwalker + snowflake, [How to explore data with PyGWalker in Python
](https://youtu.be/rprn79wfB9E?si=lAsJn1cAQnb-EklD)
| [Run in Kaggle](https://www.kaggle.com/code/lxy21495892/airbnb-eda-pygwalker-demo) | [Run in Colab](https://colab.research.google.com/drive/171QUQeq-uTLgSj1u-P9DQig7Md1kpXQ2?usp=sharing) |
|--------------------------------------------------------------|--------------------------------------------------------|
| [](https://www.kaggle.com/code/lxy21495892/airbnb-eda-pygwalker-demo) | [](https://colab.research.google.com/drive/171QUQeq-uTLgSj1u-P9DQig7Md1kpXQ2?usp=sharing) |
## Setup pygwalker
Before using pygwalker, make sure to install the packages through the command line using pip or conda.
### pip
```bash
pip install pygwalker
```
> **Note**
>
> For an early trial, you can install with `pip install pygwalker --upgrade` to keep your version up to date with the latest release or even `pip install pygwalker --upgrade --pre` to obtain latest features and bug-fixes.
### Conda-forge
```bash
conda install -c conda-forge pygwalker
```
or
```bash
mamba install -c conda-forge pygwalker
```
See [conda-forge feedstock](https://github.com/conda-forge/pygwalker-feedstock) for more help.
## Use pygwalker in Jupyter Notebook
### Quick Start
Import pygwalker and pandas to your Jupyter Notebook to get started.
```python
import pandas as pd
import pygwalker as pyg
```
You can use pygwalker without breaking your existing workflow. For example, you can call up PyGWalker with the dataframe loaded in this way:
```python
df = pd.read_csv('./bike_sharing_dc.csv')
walker = pyg.walk(df)
```

That's it. Now you have an interactive UI to analyze and visualize data with simple drag-and-drop operations.

Cool things you can do with PyGwalker:
+ You can change the mark type into others to make different charts, for example, a line chart:

+ To compare different measures, you can create a concat view by adding more than one measure into rows/columns.

+ To make a facet view of several subviews divided by the value in dimension, put dimensions into rows or columns to make a facets view.

+ PyGWalker contains a powerful data table, which provides a quick view of data and its distribution, profiling. You can also add filters or change the data types in the table.
<img width="1537" alt="pygwalker-data-preview" src="https://github.com/Kanaries/pygwalker/assets/22167673/e3239932-bc3c-4de3-8387-1eabf2ca3a3a">
+ You can save the data exploration result to a local file
### Better Practices
There are some important parameters you should know when using pygwalker:
+ `spec`: for save/load chart config (json string or file path)
+ `kernel_computation`: for using duckdb as computing engine which allows you to handle larger dataset faster in your local machine.
+ `use_kernel_calc`: Deprecated, use `kernel_computation` instead.
```python
df = pd.read_csv('./bike_sharing_dc.csv')
walker = pyg.walk(
df,
spec="./chart_meta_0.json", # this json file will save your chart state, you need to click save button in ui mannual when you finish a chart, 'autosave' will be supported in the future.
kernel_computation=True, # set `kernel_computation=True`, pygwalker will use duckdb as computing engine, it support you explore bigger dataset(<=100GB).
)
```
### Example in local notebook
* Notebook Code: [Click Here](https://github.com/Kanaries/pygwalker-offline-example)
* Preview Notebook Html: [Click Here](https://pygwalker-public-bucket.s3.amazonaws.com/demo.html)
### Example in cloud notebook
* [Use PyGWalker in Kaggle](https://www.kaggle.com/code/lxy21495892/airbnb-eda-pygwalker-demo)
* [Use PyGWalker in Google Colab](https://colab.research.google.com/drive/171QUQeq-uTLgSj1u-P9DQig7Md1kpXQ2?usp=sharing)
### Programmatic Export of Charts
After saving a chart from the UI, you can retrieve the image directly from Python.
```python
walker = pyg.walk(df, spec="./chart_meta_0.json")
# edit the chart in the UI and click the save button
walker.save_chart_to_file("Chart 1", "chart1.svg", save_type="svg")
png_bytes = walker.export_chart_png("Chart 1")
svg_bytes = walker.export_chart_svg("Chart 1")
```
## Use pygwalker in Streamlit
Streamlit allows you to host a web version of pygwalker without figuring out details of how web application works.
Here are some of the app examples build with pygwalker and streamlit:
+ [PyGWalker + streamlit for Bike sharing dataset](https://pygwalkerdemo-cxz7f7pt5oc.streamlit.app/)
+ [Earthquake Dashboard](https://earthquake-dashboard-pygwalker.streamlit.app/)
[](https://earthquake-dashboard-pygwalker.streamlit.app/)
```python
from pygwalker.api.streamlit import StreamlitRenderer
import pandas as pd
import streamlit as st
# Adjust the width of the Streamlit page
st.set_page_config(
page_title="Use Pygwalker In Streamlit",
layout="wide"
)
# Add Title
st.title("Use Pygwalker In Streamlit")
# You should cache your pygwalker renderer, if you don't want your memory to explode
@st.cache_resource
def get_pyg_renderer() -> "StreamlitRenderer":
df = pd.read_csv("./bike_sharing_dc.csv")
# If you want to use feature of saving chart config, set `spec_io_mode="rw"`
return StreamlitRenderer(df, spec="./gw_config.json", spec_io_mode="rw")
renderer = get_pyg_renderer()
renderer.explorer()
```
## [API Reference](https://pygwalker-docs.vercel.app/api-reference/jupyter)
### [pygwalker.walk](https://pygwalker-docs.vercel.app/api-reference/jupyter#walk)
| Parameter | Type | Default | Description |
|------------------------|-----------------------------------------------------------|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|
| dataset | Union[DataFrame, Connector] | - | The dataframe or connector to be used. |
| gid | Union[int, str] | None | ID for the GraphicWalker container div, formatted as 'gwalker-{gid}'. |
| env | Literal['Jupyter', 'JupyterWidget'] | 'JupyterWidget' | Environment using pygwalker. |
| field_specs | Optional[Dict[str, FieldSpec]] | None | Specifications of fields. Will be automatically inferred from `dataset` if not specified. |
| hide_data_source_config | bool | True | If True, hides DataSource import and export button. |
| theme_key | Literal['vega', 'g2'] | 'g2' | Theme type for the GraphicWalker. |
| appearance | Literal['media', 'light', 'dark'] | 'media' | Theme setting. 'media' will auto-detect the OS theme. |
| spec | str | "" | Chart configuration data. Can be a configuration ID, JSON, or remote file URL. |
| use_preview | bool | True | If True, uses the preview function. |
| kernel_computation | bool | False | If True, uses kernel computation for data. |
| **kwargs | Any | - | Additional keyword arguments. |
## Development
Refer it: [local-development](https://docs.kanaries.net/pygwalker/installation#local-development)
## Tested Environments
- [x] Jupyter Notebook
- [x] Google Colab
- [x] Kaggle Code
- [x] Jupyter Lab
- [x] Jupyter Lite
- [x] Databricks Notebook (Since version `0.1.4a0`)
- [x] Jupyter Extension for Visual Studio Code (Since version `0.1.4a0`)
- [x] Most web applications compatiable with IPython kernels. (Since version `0.1.4a0`)
- [x] **Streamlit (Since version `0.1.4.9`)**, enabled with `pyg.walk(df, env='Streamlit')`
- [x] DataCamp Workspace (Since version `0.1.4a0`)
- [x] Panel. See [panel-graphic-walker](https://github.com/panel-extensions/panel-graphic-walker).
- [x] marimo (Since version `0.4.9.11`)
- [ ] Hex Projects
- [ ] ...feel free to raise an issue for more environments.
## Configuration And Privacy Policy(pygwalker >= 0.3.10)
You can use `pygwalker config` to set your privacy configuration.
```bash
$ pygwalker config --help
usage: pygwalker config [-h] [--set [key=value ...]] [--reset [key ...]] [--reset-all] [--list]
Modify configuration file. (default: ~/Library/Application Support/pygwalker/config.json)
Available configurations:
- privacy ['offline', 'update-only', 'events'] (default: events).
"offline": fully offline, no data is send or api is requested
"update-only": only check whether this is a new version of pygwalker to update
"events": share which events about which feature is used in pygwalker, it only contains events data about which feature you arrive for product optimization. No DATA YOU ANALYSIS IS SEND. Events data will bind with a unique id, which is generated by pygwalker when it is installed based on timestamp. We will not collect any other information about you.
- kanaries_token ['your kanaries token'] (default: empty string).
your kanaries token, you can get it from https://kanaries.net.
refer: https://space.kanaries.net/t/how-to-get-api-key-of-kanaries.
by kanaries token, you can use kanaries service in pygwalker, such as share chart, share config.
options:
-h, --help show this help message and exit
--set [key=value ...]
Set configuration. e.g. "pygwalker config --set privacy=update-only"
--reset [key ...] Reset user configuration and use default values instead. e.g. "pygwalker config --reset privacy"
--reset-all Reset all user configuration and use default values instead. e.g. "pygwalker config --reset-all"
--list List current used configuration.
```
More details, refer it: [How to set your privacy configuration?](https://github.com/Kanaries/pygwalker/wiki/How-to-set-your-privacy-configuration%3F)
# License
[Apache License 2.0](https://github.com/Kanaries/pygwalker/blob/main/LICENSE)
# Contribution Guideline
You are encouraged to contribute to PyGWalker in any way that suits your interests. This may include:
- Answering questions and providing support
- Sharing ideas for new features
- Reporting bugs and glitches
- Contributing code to the project
- Offering suggestions for website improvements and better documentation
# Resources
> PyGWalker Cloud is released! You can now save your charts to cloud, publish the interactive cell as a web app and use advanced GPT-powered features. Check out the [PyGWalker Cloud](https://kanaries.net/pygwalker?from=gh_md) for more details.
+ Check out more resources about PyGWalker on [Kanaries PyGWalker](https://kanaries.net/pygwalker)
+ PyGWalker Paper [PyGWalker: On-the-fly Assistant for Exploratory Visual Data Analysis
](https://arxiv.org/abs/2406.11637)
+ We are also working on [RATH](https://kanaries.net): an Open Source, Automate exploratory data analysis software that redefines the workflow of data wrangling, exploration and visualization with AI-powered automation. Check out the [Kanaries website](https://kanaries.net) and [RATH GitHub](https://github.com/Kanaries/Rath) for more!
+ [Youtube: How to explore data with PyGWalker in Python
](https://youtu.be/rprn79wfB9E?si=lAsJn1cAQnb-EklD)
+ [Use pygwalker to build visual analysis app in streamlit](https://docs.kanaries.net/pygwalker/use-pygwalker-with-streamlit)
+ Use [panel-graphic-walker](https://github.com/panel-extensions/panel-graphic-walker) to build data visualization apps with Panel.
+ If you encounter any issues and need support, please join our [Discord](https://discord.gg/Z4ngFWXz2U) channel or raise an issue on github.
+ Share pygwalker on these social media platforms if you like it!
[](https://reddit.com/submit?url=https://github.com/Kanaries/pygwalker&title=Say%20Hello%20to%20pygwalker%3A%20Combining%20Jupyter%20Notebook%20with%20a%20Tableau-like%20UI)
[](https://news.ycombinator.com/submitlink?u=https://github.com/Kanaries/pygwalker)
[](https://twitter.com/share?url=https://github.com/Kanaries/pygwalker&text=Say%20Hello%20to%20pygwalker%3A%20Combining%20Jupyter%20Notebook%20with%20a%20Tableau-alternative%20UI)
[](https://www.facebook.com/sharer/sharer.php?u=https://github.com/Kanaries/pygwalker)
[](https://www.linkedin.com/shareArticle?url=https://github.com/Kanaries/pygwalker&&title=Say%20Hello%20to%20pygwalker%3A%20Combining%20Jupyter%20Notebook%20with%20a%20Tableau-alternative%20UI)
================================================
FILE: app/.gitignore
================================================
node_modules/
!lib
================================================
FILE: app/components.json
================================================
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/index.css",
"baseColor": "zinc",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
================================================
FILE: app/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PyGWalker App Preview</title>
</head>
<body>
<div id="pygwalker-app"></div>
<script type="module">
import PyGWalkerApp from './src/index';
window.addEventListener('message', function(event) {
if (event.data.type == "pyg_props") {
PyGWalkerApp.GWalker(event.data.data, "pygwalker-app");
}
}, false);
</script>
</body>
</html>
================================================
FILE: app/package.json
================================================
{
"name": "pygwalker-app",
"version": "0.0.1",
"main": "index.ts",
"license": "Apache License 2.0",
"private": true,
"scripts": {
"build": "vite build && vite build --mode=dsl_to_workflow && vite build --mode=vega_to_dsl",
"build:app": "vite build",
"dev:preinstall": "(cd ../graphic-walker/packages/graphic-walker; yarn --frozen-lockfile && yarn build)",
"dev:server": "vite --host",
"dev": "vite",
"test:front_end": "vite --host",
"test": "npm run test:front_end",
"serve": "vite preview"
},
"dependencies": {
"@anywidget/react": "^0.0.8",
"@headlessui/react": "^1.7.14",
"@heroicons/react": "^2.0.8",
"@kanaries/graphic-walker": "0.5.0-alpha.2",
"@kanaries/gw-dsl-parser": "0.1.49",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11",
"@segment/analytics-next": "^1.69.0",
"autoprefixer": "^10.3.5",
"buffer": "^6.0.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"html-to-image": "^1.11.11",
"lucide-react": "^0.341.0",
"mobx": "^6.9.0",
"mobx-react-lite": "^3.4.3",
"postcss": "^8.3.7",
"react": "19.x",
"react-dom": "19.x",
"react-syntax-highlighter": "^15.5.0",
"streamlit-component-lib": "^2.0.0",
"styled-components": "^5.3.6",
"tailwind-merge": "^1.14.0",
"tailwindcss": "^3.2.4",
"tailwindcss-animate": "^1.0.7",
"uuid": "^8.3.2"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^24.0.x",
"@rollup/plugin-replace": "^5.0.x",
"@rollup/plugin-terser": "^0.4.x",
"@rollup/plugin-typescript": "^11.0.x",
"@types/node": "^20.7.2",
"@types/react": "^19.x",
"@types/react-dom": "^19.x",
"@types/react-syntax-highlighter": "^15.5.7",
"@types/styled-components": "^5.1.26",
"@vitejs/plugin-react": "^3.1.x",
"typescript": "^5.4.2",
"vite": "4.1.5",
"vite-plugin-wasm": "^3.2.2"
},
"resolutions": {
"react": "^19.x",
"react-dom": "^19.x",
"@types/react": "^19.x",
"@types/react-dom": "^19.x"
},
"peerDependencies": {},
"prettier": {
"tabWidth": 4,
"printWidth": 160
}
}
================================================
FILE: app/postcss.config.js
================================================
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
================================================
FILE: app/src/components/codeExportModal/index.tsx
================================================
import React, { useEffect, useState, useCallback } from "react";
import { observer } from "mobx-react-lite";
import { Light as SyntaxHighlighter } from "react-syntax-highlighter";
import json from "react-syntax-highlighter/dist/esm/languages/hljs/json";
import py from "react-syntax-highlighter/dist/esm/languages/hljs/python";
import atomOneLight from "react-syntax-highlighter/dist/esm/styles/hljs/atom-one-light";
import atomOneDark from "react-syntax-highlighter/dist/esm/styles/hljs/atom-one-dark";
import type { VizSpecStore } from "@kanaries/graphic-walker/store/visualSpecStore";
import type { IChart } from "@kanaries/graphic-walker/interfaces";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import commonStore from "@/store/common";
import { darkModeContext } from "@/store/context";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { tracker } from "@/utils/tracker";
import { usePythonCode } from "./usePythonCode";
SyntaxHighlighter.registerLanguage("json", json);
SyntaxHighlighter.registerLanguage("python", py);
interface ICodeExport {
globalStore: React.MutableRefObject<VizSpecStore | null>;
sourceCode: string;
open: boolean;
setOpen: (open: boolean) => void;
}
const CodeExport: React.FC<ICodeExport> = observer((props) => {
const { globalStore, sourceCode, open, setOpen } = props;
const [visSpec, setVisSpec] = useState<IChart[]>([]);
const [tips, setTips] = useState<string>("");
const darkMode = React.useContext(darkModeContext);
const { pyCode } = usePythonCode({
sourceCode,
visSpec,
version: commonStore.version,
});
const closeModal = useCallback(() => {
setOpen(false);
}, [setOpen]);
const copyToCliboard = async (content: string) => {
try {
navigator.clipboard.writeText(content);
setOpen(false);
} catch(e) {
setTips("The Clipboard API has been blocked in this environment. Please copy manully.");
}
};
useEffect(() => {
if (open && globalStore.current) {
const res = globalStore.current.exportCode();
setVisSpec(res);
}
}, [open]);
return (
<Dialog
open={open}
modal={false}
onOpenChange={(show) => {
setOpen(show);
}}
>
<DialogContent className="sm:max-w-[90%] lg:max-w-[900px]">
<DialogHeader>
<DialogTitle>Code Export</DialogTitle>
<DialogDescription>
Export the code of all charts in PyGWalker.
</DialogDescription>
</DialogHeader>
<div className="text-sm max-h-64 overflow-auto p-1">
<Tabs defaultValue="python" className="w-full">
<TabsList>
<TabsTrigger value="python">Python</TabsTrigger>
<TabsTrigger value="json">JSON(Graphic Walker)</TabsTrigger>
</TabsList>
<TabsContent className="py-4" value="python">
<h3 className="text-sm font-medium mb-2">PyGWalker Code</h3>
<SyntaxHighlighter showLineNumbers language="python" style={darkMode === 'dark' ? atomOneDark : atomOneLight}>
{pyCode}
</SyntaxHighlighter>
<div className="text-xs max-h-56 mt-2">
<p>{tips}</p>
</div>
<div className="mt-4 flex justify-start gap-2">
<Button
onClick={() => {
copyToCliboard(pyCode);
}}
>
Copy to Clipboard
</Button>
<Button variant="outline" onClick={closeModal}>
Cancel
</Button>
</div>
</TabsContent>
<TabsContent value="json">
<h3 className="text-sm font-medium mb-2">Graphic Walker Specification</h3>
<SyntaxHighlighter showLineNumbers language="json" style={darkMode === 'dark' ? atomOneDark : atomOneLight}>
{JSON.stringify(visSpec, null, 2)}
</SyntaxHighlighter>
<div className="text-xs max-h-56 mt-2">
<p>{tips}</p>
</div>
<div className="mt-4 flex justify-start gap-2">
<Button
onClick={() => {
copyToCliboard(JSON.stringify(visSpec, null, 2));
tracker.track("click", {"entity": "copy_code_button"});
}}
>
Copy to Clipboard
</Button>
<Button variant="outline" onClick={closeModal}>Cancel</Button>
</div>
</TabsContent>
</Tabs>
</div>
</DialogContent>
</Dialog>
);
});
export default CodeExport;
================================================
FILE: app/src/components/codeExportModal/usePythonCode.ts
================================================
import { chartToWorkflow } from "@kanaries/graphic-walker/utils/workflow";
import type { IChart } from "@kanaries/graphic-walker/interfaces";
import { useMemo } from "react"
export function usePythonCode (props: {
sourceCode: string;
visSpec: IChart[];
version: string;
}) {
const { sourceCode, visSpec, version } = props;
const pygConfig = useMemo(() => {
return JSON.stringify({
"config": visSpec,
"chart_map": {},
"workflow_list": visSpec.map((spec) => chartToWorkflow(spec)),
version
})
}, [visSpec])
const pyCode = useMemo(() => {
const preCode = sourceCode.replace("'____pyg_walker_spec_params____'", "vis_spec")
return `vis_spec = r"""${pygConfig}"""\n${preCode}`;
}, [sourceCode, pygConfig])
return {
pyCode
}
}
================================================
FILE: app/src/components/initModal/index.tsx
================================================
import React from "react";
import { observer } from "mobx-react-lite";
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
import commonStore from "../../store/common";
interface IInitModal {}
const InitModal: React.FC<IInitModal> = observer((props) => {
return (
<Dialog open={commonStore.initModalOpen} modal={true}>
<DialogContent hideClose>
<DialogTitle className="sr-only">{commonStore.initModalInfo.title}</DialogTitle>
<div className="flex justify-between mb-1">
<span className="text-base font-medium text-blue-700 dark:text-white">{commonStore.initModalInfo.title}</span>
<span className="text-sm font-medium text-blue-700 dark:text-white">
{commonStore.initModalInfo.curIndex} / {commonStore.initModalInfo.total}
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700">
<div
className="bg-blue-600 h-2.5 rounded-full"
style={{ width: `${Math.floor((commonStore.initModalInfo.curIndex / commonStore.initModalInfo.total) * 100)}%` }}
></div>
</div>
</DialogContent>
</Dialog>
);
});
export default InitModal;
================================================
FILE: app/src/components/options.tsx
================================================
import React, { useEffect, useState } from "react";
import type { IAppProps } from "../interfaces";
const copyToClipboard = async (text: string) => {
return navigator.clipboard.writeText(text);
};
interface ISolutionProps {
header: string;
cmd: string;
}
const updateSolutions: ISolutionProps[] = [
{
header: "Using pip:",
cmd: "pip install pygwalker --upgrade",
},
{
header: "Using anaconda:",
cmd: "conda install -c conda-forge pygwalker",
},
];
const Solution: React.FC<ISolutionProps> = (props) => {
const [busy, setBusy] = useState(false);
return (
<>
<header>{props.header}</header>
<div>
<code>{props.cmd}</code>
<div
role="button"
aria-label="Copy command"
tabIndex={0}
aria-disabled={busy}
onClick={() => {
if (busy) {
return;
}
setBusy(true);
copyToClipboard(props.cmd).finally(() => {
setTimeout(() => {
setBusy(false);
}, 2_000);
});
}}
>
<small>{busy ? "Copied" : "Copy"}</small>
<span>{busy ? "\u2705" : "\u274f"}</span>
</div>
</div>
</>
);
};
const RAND_HASH = Math.random().toString(16).split(".").at(1) + new Date().getTime().toString(16).padStart(16, "0");
const Options: React.FC<IAppProps> = (props: IAppProps) => {
const [outdated, setOutDated] = useState<Boolean>(false);
const [appMeta, setAppMeta] = useState<any>({});
const [showUpdateHint, setShowUpdateHint] = useState(false);
if (window.localStorage.getItem("HASH") === null) {
window.localStorage.setItem("HASH", RAND_HASH);
}
const UPDATE_URL = "https://5agko11g7e.execute-api.us-west-1.amazonaws.com/default/check_updates";
const VERSION = (window as any)?.__GW_VERSION || "current";
const HASH = window.localStorage.getItem("HASH");
useEffect(() => {
if (props.userConfig?.privacy !== "offline") {
const req = `${UPDATE_URL}?pkg=pygwalker-app&v=${VERSION}&hashcode=${HASH}&env=${process.env.NODE_ENV}&kernelHashcode=${props.hashcode}`;
fetch(req, {
headers: {
"Content-Type": "application/json",
},
})
.then((resp) => resp.json())
.then((res) => {
setAppMeta({ data: res.data });
setOutDated(res?.data?.outdated || false);
});
}
}, []);
useEffect(() => {
setShowUpdateHint(false);
}, [outdated]);
useEffect(() => {
if (!showUpdateHint) {
return;
}
const handleDismiss = () => {
setShowUpdateHint(false);
};
document.addEventListener("click", handleDismiss);
return () => {
document.removeEventListener("click", handleDismiss);
};
}, [showUpdateHint]);
useEffect(() => {
setShowUpdateHint(false);
}, [outdated]);
useEffect(() => {
if (!showUpdateHint) {
return;
}
const handleDismiss = () => {
setShowUpdateHint(false);
};
document.addEventListener("click", handleDismiss);
return () => {
document.removeEventListener("click", handleDismiss);
};
}, [showUpdateHint]);
return (
<>
<style>{`
.update_link {
position: fixed;
right: 2rem;
top: 1rem;
background-color: rgba(56,189,248,.1);
color: rgb(2, 132, 199);
--line-height: 1.25rem;
line-height: var(--line-height);
--padding-block: 0.25rem;
padding: var(--padding-block) 0.75rem;
border-radius: calc(var(--padding-block) + var(--line-height) * .5);
font-weight: 500;
font-size: .75rem;
font-family: sans-serif;
text-align: end;
}
.update_link p {
margin: 0;
padding: 0;
}
.update_link *[role="button"] {
cursor: pointer;
}
.update_link *[role="separator"] {
user-select: none;
margin-inline: 0.5em;
display: inline-block;
}
.update_link .solutions {
margin: 1em 0 1em 2em;
display: grid;
grid-template-columns: repeat(2, auto);
gap: 0.2em 0.8em;
font-family: monospace;
}
.update_link .solutions > * {
display: flex;
align-items: center;
}
.update_link .solutions div:has(> code) {
display: flex;
align-items: center;
background-color: #777;
color: #eee;
padding-inline: 0.8em 6em;
padding-block: 0.1em;
border-radius: 2px;
position: relative;
height: 2em;
}
.update_link *[aria-disabled="true"] {
opacity: 1 !important;
cursor: default !important;
}
.update_link .solutions div *[role="button"] {
user-select: none;
flex-grow: 0;
flex-shrink: 0;
cursor: pointer;
margin-left: 1em;
background-color: #eee;
color: #444;
padding-inline: 0.4em;
font-size: 0.9rem;
display: flex;
align-items: center;
border-radius: 1em;
opacity: 0.5;
position: absolute;
right: 0.5em;
z-index: 10;
font-family:
}
.update_link .solutions div:hover *[role="button"] {
opacity: 1;
}
.update_link .solutions div *[role="button"] > small {
font-size: 0.65rem;
margin-right: 0.5em;
}
.update_link .solutions div > code {
flex-grow: 1;
flex-shrink: 1;
text-align: start;
}
.update_link a {
font-family: monospace;
color: inherit;
text-decoration: none;
cursor: pointer;
}
.update_link a span {
filter: brightness(1.8);
}
.update_link p span {
font-family: monospace;
color: inherit;
}
@media (prefers-color-scheme: dark) {
.update_link span, .update_link header, .update_link a {
filter: brightness(1.5);
}
}
`}</style>
{outdated && (
<div className="update_link" aria-live="assertive" role="alert" tabIndex={0} onClick={(e) => e.stopPropagation()}>
<p>
<a href="https://pypi.org/project/pygwalker" target="_blank">
{"Update: "}
{`${VERSION}\u2191`}
<span>{` ${appMeta?.data?.latest?.release?.version || "latest"}`}</span>
</a>
<span role="separator">|</span>
<span aria-haspopup role="button" tabIndex={0} onClick={() => setShowUpdateHint((s) => !s)}>
{`${showUpdateHint ? "Hide" : " Cmd"} \u274f`}
</span>
</p>
{showUpdateHint && (
<div className="solutions">
{updateSolutions.map((sol, i) => (
<Solution key={i} {...sol} />
))}
</div>
)}
</div>
)}
</>
);
};
export default Options;
================================================
FILE: app/src/components/preview/index.tsx
================================================
import React from "react";
import { observer } from "mobx-react-lite";
import { PureRenderer, IRow } from '@kanaries/graphic-walker';
import type { IDarkMode, IThemeKey } from '@kanaries/graphic-walker/interfaces';
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
// @ts-ignore
import style from '@/index.css?inline'
interface IPreviewProps {
gid: string;
themeKey: string;
dark: IDarkMode;
charts: {
visSpec: any;
data: IRow[];
}[];
}
const Preview: React.FC<IPreviewProps> = observer((props) => {
const { charts, themeKey } = props;
return (
<React.StrictMode>
<div className="bg-background text-foreground">
<style>{style}</style>
<Tabs defaultValue="0" className="w-full">
<div className="overflow-x-auto max-w-full">
<TabsList>
{charts.map((chart, index) => {
return <TabsTrigger key={index} value={index.toString()}>{chart.visSpec.name}</TabsTrigger>
})}
</TabsList>
</div>
{charts.map((chart, index) => {
return <TabsContent key={index} value={index.toString()}>
<PureRenderer
vizThemeConfig={themeKey as IThemeKey}
name={chart.visSpec.name}
visualConfig={chart.visSpec.config}
visualLayout={chart.visSpec.layout}
visualState={chart.visSpec.encodings}
type='remote'
computation={async(_) => { return chart.data }}
appearance={props.dark as IDarkMode}
/>
</TabsContent>
})}
</Tabs>
</div>
</React.StrictMode>
);
});
interface IChartPreviewProps {
themeKey: string;
dark: IDarkMode;
visSpec: any;
data: IRow[];
title: string;
desc: string;
}
const ChartPreview: React.FC<IChartPreviewProps> = observer((props) => {
return (
<React.StrictMode>
<div>
<h1 style={{marginTop: "1rem", color: "#333", fontSize: "1.1rem", marginBottom: "0.5rem", paddingInlineStart: "1rem"}}>{ props.title }</h1>
<p style={{color: "#666", fontWeight: 300, paddingInlineStart: "1rem"}}>{ props.desc }</p>
<PureRenderer
vizThemeConfig={props.themeKey as IThemeKey}
name={props.visSpec.name}
visualConfig={props.visSpec.config}
visualLayout={props.visSpec.layout}
visualState={props.visSpec.encodings}
type='remote'
computation={async(_) => { return props.data }}
appearance={props.dark as IDarkMode}
/>
</div>
</React.StrictMode>
);
});
export {
Preview,
ChartPreview,
};
export type {
IPreviewProps,
IChartPreviewProps
}
================================================
FILE: app/src/components/runcellBanner/index.tsx
================================================
import React, { useState } from "react";
import { tracker } from "@/utils/tracker";
const RUNCELL_LOGO_URL = "https://www.runcell.dev/runcell-logo.svg";
const RUNCELL_WEBSITE = "https://www.runcell.dev?utm_source=pygwalker";
const LLM_LOGOS = [
"https://www.runcell.dev/llm-icons/openai.svg",
"https://www.runcell.dev/llm-icons/claude-color.svg",
"https://www.runcell.dev/llm-icons/gemini-color.svg",
"https://www.runcell.dev/llm-icons/deepseek-color.svg",
];
interface RuncellBannerProps {
env?: string;
}
const checkRuncellInstalled = (): boolean => {
try {
// Runcell JupyterLab plugin stores user status in localStorage
const runcellUser = window.parent.localStorage.getItem("plugin_auth_user_v2");
return runcellUser !== null;
} catch {
return false;
}
};
export const RuncellBanner: React.FC<RuncellBannerProps> = ({ env }) => {
const [dismissed, setDismissed] = useState(false);
// Only show in Jupyter environments
if (env !== "jupyter_widgets" && env !== "anywidget") {
return null;
}
// Don't show if runcell is installed or user dismissed
if (checkRuncellInstalled() || dismissed) {
return null;
}
const handleClick = () => {
tracker.track("click", { entity: "runcell_banner" });
window.open(RUNCELL_WEBSITE, "_blank");
};
const handleDismiss = (e: React.MouseEvent) => {
e.stopPropagation();
tracker.track("click", { entity: "runcell_banner_dismiss" });
setDismissed(true);
};
return (
<div
className="flex items-center justify-between gap-3 px-4 py-2 mb-2 rounded-md bg-gradient-to-r from-purple-50 to-blue-50 dark:from-purple-950/30 dark:to-blue-950/30 border border-purple-200 dark:border-purple-800 cursor-pointer hover:shadow-md transition-shadow"
onClick={handleClick}
>
<div className="flex items-center gap-3">
<img
src={RUNCELL_LOGO_URL}
alt="Runcell"
className="w-6 h-6"
/>
<span className="text-sm text-gray-700 dark:text-gray-300">
Enable AI Agent for data analysis with pip install runcell
</span>
</div>
<div className="flex items-center gap-2">
<div className="flex items-center gap-1 px-2 py-1 rounded-full bg-white/80">
{LLM_LOGOS.map((logo, index) => (
<img
key={index}
src={logo}
alt="LLM"
className="w-4 h-4"
/>
))}
</div>
<button
onClick={handleDismiss}
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 text-lg leading-none px-1"
aria-label="Dismiss"
>
×
</button>
</div>
</div>
);
};
export default RuncellBanner;
================================================
FILE: app/src/components/ui/badge.tsx
================================================
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }
================================================
FILE: app/src/components/ui/button.tsx
================================================
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
================================================
FILE: app/src/components/ui/checkbox.tsx
================================================
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { CheckIcon } from "@radix-ui/react-icons"
import { cn } from "@/lib/utils"
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<CheckIcon className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
export { Checkbox }
================================================
FILE: app/src/components/ui/dialog.tsx
================================================
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { Cross2Icon } from "@radix-ui/react-icons"
import { cn } from "@/lib/utils"
import { portalContainerContext } from "@/store/context"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
{ hideClose?: boolean } & React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, hideClose, ...props }, ref) => (
<DialogPortal container={React.useContext(portalContainerContext)}>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
{ !hideClose && (
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight text-muted-foreground",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}
================================================
FILE: app/src/components/ui/input.tsx
================================================
import * as React from "react"
import { cn } from "@/lib/utils"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }
================================================
FILE: app/src/components/ui/label.tsx
================================================
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }
================================================
FILE: app/src/components/ui/select.tsx
================================================
import * as React from "react"
import {
CaretSortIcon,
CheckIcon,
ChevronDownIcon,
ChevronUpIcon,
} from "@radix-ui/react-icons"
import * as SelectPrimitive from "@radix-ui/react-select"
import { cn } from "@/lib/utils"
import { portalContainerContext } from "@/store/context"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<CaretSortIcon className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUpIcon />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDownIcon />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal container={React.useContext(portalContainerContext)}>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
}
================================================
FILE: app/src/components/ui/tabs.tsx
================================================
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }
================================================
FILE: app/src/components/ui/toggle-group.tsx
================================================
import * as React from "react"
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
import { VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { toggleVariants } from "@/components/ui/toggle"
const ToggleGroupContext = React.createContext<
VariantProps<typeof toggleVariants>
>({
size: "default",
variant: "default",
})
const ToggleGroup = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
VariantProps<typeof toggleVariants>
>(({ className, variant, size, children, ...props }, ref) => (
<ToggleGroupPrimitive.Root
ref={ref}
className={cn("flex items-center justify-center gap-1", className)}
{...props}
>
<ToggleGroupContext.Provider value={{ variant, size }}>
{children}
</ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root>
))
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
const ToggleGroupItem = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
VariantProps<typeof toggleVariants>
>(({ className, children, variant, size, ...props }, ref) => {
const context = React.useContext(ToggleGroupContext)
return (
<ToggleGroupPrimitive.Item
ref={ref}
className={cn(
toggleVariants({
variant: context.variant || variant,
size: context.size || size,
}),
className
)}
{...props}
>
{children}
</ToggleGroupPrimitive.Item>
)
})
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
export { ToggleGroup, ToggleGroupItem }
================================================
FILE: app/src/components/ui/toggle.tsx
================================================
import * as React from "react"
import * as TogglePrimitive from "@radix-ui/react-toggle"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const toggleVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
{
variants: {
variant: {
default: "bg-transparent",
outline:
"border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
},
size: {
default: "h-9 px-3",
sm: "h-8 px-2",
lg: "h-10 px-3",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
const Toggle = React.forwardRef<
React.ElementRef<typeof TogglePrimitive.Root>,
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
VariantProps<typeof toggleVariants>
>(({ className, variant, size, ...props }, ref) => (
<TogglePrimitive.Root
ref={ref}
className={cn(toggleVariants({ variant, size, className }))}
{...props}
/>
))
Toggle.displayName = TogglePrimitive.Root.displayName
export { Toggle, toggleVariants }
================================================
FILE: app/src/components/uploadChartModal/index.tsx
================================================
import React, { useState, useEffect } from "react";
import { observer } from "mobx-react-lite";
import type { IGWHandler } from "@kanaries/graphic-walker/interfaces";
import type { VizSpecStore } from '@kanaries/graphic-walker/store/visualSpecStore'
import { chartToWorkflow } from "@kanaries/graphic-walker"
import { tracker } from "@/utils/tracker";
import communicationStore from "../../store/communication";
import commonStore from "../../store/common";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
interface IUploadChartModal {
gwRef: React.MutableRefObject<IGWHandler | null>;
storeRef: React.MutableRefObject<VizSpecStore | null>;
dark: string;
}
const UploadChartModal: React.FC<IUploadChartModal> = observer((props) => {
const [uploading, setUploading] = useState(false);
const [chartName, setChartName] = useState("");
const [datasetName, setDatasetName] = useState("");
const [isPublic, setIsPublic] = useState(true);
const [isCreateDashboard, setIsCreateDashboard] = useState(true);
const [instanceType, setInstanceType] = useState("");
useEffect(() => {
if (commonStore.uploadChartModalOpen) {
const instanceType = (props.storeRef.current?.exportCode().length || 0) > 1 ? "dashboard" : "chart";
setChartName(`${instanceType}-${new Date().getTime().toString(16).padStart(16, "0")}`);
setDatasetName(`dataset-${new Date().getTime().toString(16).padStart(16, "0")}`);
setIsPublic(true);
setInstanceType(instanceType);
}
}, [commonStore.uploadChartModalOpen])
const uploadSuccess = (instanceType: string, instanceId: string, datasetId: string) => {
const managerUrl = instanceType === "chart" ? `https://kanaries.net/analytics/c/${instanceId}` : `https://kanaries.net/analytics/d/${instanceId}`
const shareUrl = instanceType === "chart" ? `https://kanaries.net/analytics/chart/${instanceId}/share?theme=${props.dark}` : `https://kanaries.net/analytics/dashboard/${instanceId}/share?theme=${props.dark}`
const datsetUrl = `https://kanaries.net/analytics/detail/${datasetId}`
if (instanceType === "dashboard" && instanceId === "" ) {
commonStore.setNotification(
{
type: "success",
title: "Success",
message: (
<>
<p>Upload success, you can view and manager it at:
<a className="font-semibold" href={datsetUrl} target="_blank">
{datsetUrl}
</a>
</p>
</>
),
},
30_000
);
} else {
commonStore.setNotification(
{
type: "success",
title: "Success",
message: (
<>
<p>Upload success, you can view and manager it at:
<a className="font-semibold" href={managerUrl} target="_blank">
{managerUrl}
</a>
</p>
<br />
{isPublic && <p>Since you set the {instanceType} to public, you can also share it with others by:
<a className="font-semibold" href={shareUrl} target="_blank">
{shareUrl}
</a>
</p>}
</>
),
},
30_000
);
}
};
const onClick = async () => {
if (uploading) return;
setUploading(true);
tracker.track("click", {"entity": "upload_chart_button"});
const visSpec = props.storeRef.current?.exportCode()!;
try {
if (instanceType === "dashboard") {
const resp = await communicationStore.comm?.sendMsg(
"upload_to_cloud_dashboard",
{
chartName: chartName,
datasetName: datasetName,
isPublic: isPublic,
isCreateDashboard: isCreateDashboard,
visSpec: visSpec,
workflowList: visSpec.map(spec => chartToWorkflow(spec).workflow),
},
120_000
);
uploadSuccess(instanceType, resp?.data.dashboardId, resp?.data.datasetId);
} else {
const resp = await communicationStore.comm?.sendMsg(
"upload_to_cloud_charts",
{
chartName: chartName,
datasetName: datasetName,
isPublic: isPublic,
visSpec: visSpec,
workflow: chartToWorkflow(visSpec[0]).workflow,
},
120_000
);
uploadSuccess(instanceType, resp?.data.chartId, resp?.data.datasetId);
}
commonStore.setUploadChartModalOpen(false);
} finally {
setUploading(false);
}
};
return (
<Dialog
open={commonStore.uploadChartModalOpen}
modal={false}
onOpenChange={(show) => {
commonStore.setUploadChartModalOpen(show)
}}
>
<DialogContent>
<DialogHeader>
<DialogTitle>Upload Chart</DialogTitle>
<DialogDescription>
upload your charts to dashboard of kanaries cloud.
</DialogDescription>
</DialogHeader>
<div>
<div className="text-sm max-h-64 overflow-auto p-1 flex flex-col space-y-2">
<div className="grid w-full max-w-sm items-center gap-1.5">
<Label htmlFor="dataset-name-input">Dataset Name</Label>
<Input
value={datasetName}
onChange={(e) => {
setDatasetName(e.target.value);
}}
type="text"
placeholder="please input dataset name"
id="dataset-name-input"
className="mb-1"
/>
</div>
<div className="grid w-full max-w-sm items-center gap-1.5">
<Label htmlFor="chart-name-input">
{instanceType === "dashboard" ? "Dashboard Name" : "Chart Name"}
</Label>
<Input
value={chartName}
onChange={(e) => {
setChartName(e.target.value);
}}
disabled={!isCreateDashboard}
type="text"
placeholder="please input chart name"
id="chart-name-input"
className="mb-1"
/>
</div>
<div className="items-top flex space-x-2">
<Checkbox
id="public-checkbox"
checked={isPublic}
onCheckedChange={(checked) => {setIsPublic(checked as boolean);}}
/>
<div className="grid gap-1.5 leading-none">
<Label htmlFor="public-checkbox">Is Public?</Label>
<p className="text-sm text-muted-foreground">
Make {instanceType} publicly accessible.
</p>
</div>
</div>
{instanceType === "dashboard" && (
<div className="items-top flex space-x-2">
<Checkbox
id="create-dashboard-checkbox"
checked={isCreateDashboard}
onCheckedChange={(checked) => {setIsCreateDashboard(checked as boolean);}}
/>
<div className="grid gap-1.5 leading-none">
<Label htmlFor="create-dashboard-checkbox">Create Dashboard?</Label>
<p className="text-sm text-muted-foreground">
Create a dashboard containing all charts, or upload charts individually.
</p>
</div>
</div>
)}
</div>
<div className="mt-4 flex justify-end">
<Button variant="outline" className="mr-2 px-6" disabled={uploading} onClick={onClick}>
{uploading ? "uploading.." : "upload"}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
});
export default UploadChartModal;
================================================
FILE: app/src/components/uploadSpecModal/index.tsx
================================================
import React, { useEffect, useState } from "react";
import { observer } from "mobx-react-lite";
import type { VizSpecStore } from '@kanaries/graphic-walker/store/visualSpecStore'
import { chartToWorkflow } from "@kanaries/graphic-walker/utils/workflow";
import { tracker } from "@/utils/tracker";
import communicationStore from "../../store/communication";
import commonStore from "../../store/common";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import { badgeVariants } from "@/components/ui/badge";
interface IUploadSpecModal {
setGwIsChanged: React.Dispatch<React.SetStateAction<boolean>>;
storeRef: React.MutableRefObject<VizSpecStore | null>;
}
const UploadSpecModal: React.FC<IUploadSpecModal> = observer((props) => {
const [uploading, setUploading] = useState(false);
const [specName, setSpecName] = useState("");
const [isSetToken, setIsSetToken] = useState(false);
const [token, setToken] = useState("");
const [contentType, setContentType] = useState<"onboarding" | "upload">("onboarding");
const uploadSuccess = (path: string) => {
commonStore.setNotification(
{
type: "success",
title: "Success",
message: (
<>
<p className="py-1">upload spec success, please use</p>
<p className="font-semibold px-1 py-1">`pyg.walk(df, spec="ksf://{path}")`</p>
<p className="py-1">to rerun pygwalker.</p>
</>
),
},
30_000
);
};
const onClick = async () => {
if (uploading) return;
tracker.track("click", {"entity": "upload_spec_to_cloud_button"});
setUploading(true);
try {
const resp = await communicationStore.comm?.sendMsg(
"upload_spec_to_cloud",
{"fileName": specName, "newToken": isSetToken ? token : ""},
30_000
);
commonStore.setUploadSpecModalOpen(false);
uploadSuccess(resp?.data["specFilePath"]);
props.setGwIsChanged(false);
} finally {
setUploading(false);
}
};
const onClickSetToken = (checked: boolean) => {
setIsSetToken(checked);
setToken("");
}
const saveSpecToLocal = () => {
tracker.track("click", {"entity": "save_spec_to_local_file_button"});
const visSpec = props.storeRef.current?.exportCode();
const configObj = {
config: visSpec,
chart_map: {},
version: commonStore.version,
workflow_list: visSpec?.map(spec => chartToWorkflow(spec).workflow),
}
const blob = new Blob([JSON.stringify(configObj)], {type: "text/plain;charset=utf-8"});
const url = URL.createObjectURL(blob);
const tempLink = document.createElement("a");
tempLink.href = url;
tempLink.download = `pygwalker_spec_${new Date().getTime()}.json`
tempLink.click();
URL.revokeObjectURL(url);
commonStore.setUploadSpecModalOpen(false);
props.setGwIsChanged(false);
};
useEffect(() => {
setContentType("onboarding");
}, [commonStore.uploadSpecModalOpen]);
const OnboardingContent = (
<DialogContent>
<DialogHeader>
<DialogTitle>Save Spec</DialogTitle>
</DialogHeader>
<div className="grid h-full grid-rows-2 gap-6 lg:grid-cols-2 lg:grid-rows-1">
<button
className={"flex flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent"}
onClick={() => {
setContentType("upload");
tracker.track("click", {"entity": "select_upload_spec_to_cloud_button"});
}}
>
<div className="flex items-center justify-center h-[160px] w-full">
<span className="font-semibold">upload as cloud file</span>
</div>
</button>
<button
className={"flex flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent"}
onClick={saveSpecToLocal}
>
<div className="flex items-center justify-center h-[160px] w-full">
<span className="font-semibold">save as local file</span>
</div>
</button>
</div>
</DialogContent>
)
const UpdateSpecContent = (
<DialogContent>
<DialogHeader>
<DialogTitle>Upload Sepc</DialogTitle>
<DialogDescription>
<p className="py-1">Because you currently don't pass in the spec parameter or the passed in spec parameter is not a writable spec file.</p>
<p className="py-1">Currently the spec configuration is already in your ui cache, you may need to upload kanaries cloud to save it.</p>
<p className="py-1">If you don't have kanaries_token, you need to get it: <a className={badgeVariants({ variant: "outline" })} href="https://kanaries.net/analytics/settings?tab=apikey" target="_blank">Kanaries</a></p>
</DialogDescription>
</DialogHeader>
<div>
<div className="text-sm max-h-64 overflow-auto p-1">
<Input
value={specName}
onChange={(e) => {
setSpecName(e.target.value);
}}
type="text"
placeholder="please input spec file name"
id="chart-name-input"
className="mb-1"
/>
</div>
<div className="flex items-center justify-end mt-2">
<Checkbox
id="link-checkbox"
checked={isSetToken}
onCheckedChange={(checked) => {
onClickSetToken(checked as boolean);
}}
/>
<Label className="ml-2">Set a new kanaries_token?</Label>
</div>
{
isSetToken && (
<div className="text-sm max-h-64 overflow-auto p-1 mt-2">
<Input
value={token}
onChange={(e) => {
setToken(e.target.value);
}}
type="text"
autoComplete="off"
placeholder="please input new kanaries token"
id="token-input"
/>
</div>
)
}
<div className="mt-4 flex justify-end">
<Button variant="outline" className="mr-2 px-6" disabled={uploading} onClick={onClick}>
{uploading ? "uploading.." : "upload"}
</Button>
</div>
</div>
</DialogContent>
)
return (
<Dialog
open={commonStore.uploadSpecModalOpen}
modal={false}
onOpenChange={(show) => {
commonStore.setUploadSpecModalOpen(show);
}}
>
{contentType === "upload" && UpdateSpecContent }
{contentType === "onboarding" && OnboardingContent }
</Dialog>
);
});
export default UploadSpecModal;
================================================
FILE: app/src/dataSource/index.tsx
================================================
import type { IDataSourceProps } from "../interfaces";
import type { IRow, IDataQueryPayload } from "@kanaries/graphic-walker/interfaces";
import commonStore from "../store/common";
import communicationStore from "../store/communication"
import { parser_dsl_with_meta } from "@kanaries/gw-dsl-parser";
interface MessagePayload extends IDataSourceProps {
action: "requestData" | "postData" | "finishData";
dataSourceId: string;
partId: number;
curIndex: number;
total: number;
data?: IRow[];
}
interface ICommPostDataMessage {
dataSourceId: string;
data?: IRow[];
total: number;
curIndex: number;
}
export async function loadDataSource(props: IDataSourceProps): Promise<IRow[]> {
const { dataSourceId } = props;
return new Promise((resolve, reject) => {
const data = new Array<IRow>();
const timeout = () => {
reject("timeout");
};
let timer = setTimeout(timeout, 100_000);
const onmessage = (ev: MessageEvent<MessagePayload>) => {
try {
if (ev.data.dataSourceId === dataSourceId) {
clearTimeout(timer);
timer = setTimeout(timeout, 100_000);
if (ev.data.action === "postData") {
commonStore.setInitModalOpen(true);
commonStore.setInitModalInfo({
total: ev.data.total,
curIndex: ev.data.curIndex,
title: "Loading Data",
});
data.push(...(ev.data.data ?? []));
} else if (ev.data.action === "finishData") {
window.removeEventListener("message", onmessage);
resolve(data);
}
}
} catch (err) {
reject({ message: "handler", error: err });
}
};
window.addEventListener("message", onmessage);
});
}
export function postDataService(msg: ICommPostDataMessage) {
window.postMessage(
{
action: "postData",
dataSourceId: msg.dataSourceId,
total: msg.total,
curIndex: msg.curIndex,
data: msg.data,
} as MessagePayload,
"*"
)
}
export function finishDataService(msg: any) {
window.postMessage(
{
action: "finishData",
dataSourceId: msg.dataSourceId,
} as MessagePayload,
"*"
)
}
interface IBatchGetDatasTask {
query: any;
resolve: (value: any) => void;
reject: (reason?: any) => void;
}
function initBatchGetDatas(action: string) {
const taskList = [] as IBatchGetDatasTask[];
const batchGetDatas = async(taskList: IBatchGetDatasTask[]) => {
const result = await communicationStore.comm?.sendMsg(
action,
{"queryList": taskList.map(task => task.query)},
60_000
);
if (result) {
for (let i = 0; i < taskList.length; i++) {
taskList[i].resolve(result["data"]["datas"][i]);
}
} else {
for (let i = 0; i < taskList.length; i++) {
taskList[i].reject("get result error");
}
}
}
const getDatas = (query: any) => {
return new Promise<any>((resolve, reject) => {
taskList.push({ query, resolve, reject });
if (taskList.length === 1) {
setTimeout(() => {
batchGetDatas(taskList.splice(0, taskList.length));
}, 100);
}
})
}
return {
getDatas
}
}
const batchGetDatasBySql = initBatchGetDatas("batch_get_datas_by_sql");
const batchGetDatasByPayload = initBatchGetDatas("batch_get_datas_by_payload");
const DEFAULT_LIMIT = 50_000;
function notifyDataLimit() {
commonStore.setNotification({
type: "warning",
title: "Data Limit Reached",
message: (<>
The current computation has returned more than 50,000 rows of data.
To ensure optimal performance, we are currently rendering only the first 50,000 rows.
If you need to render the entire all datas, please use the 'limit' tool
(<svg className="inline bg-black" width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M9.94969 7.49989C9.94969 8.85288 8.85288 9.94969 7.49989 9.94969C6.14691 9.94969 5.0501 8.85288 5.0501 7.49989C5.0501 6.14691 6.14691 5.0501 7.49989 5.0501C8.85288 5.0501 9.94969 6.14691 9.94969 7.49989ZM10.8632 8C10.6213 9.64055 9.20764 10.8997 7.49989 10.8997C5.79214 10.8997 4.37847 9.64055 4.13662 8H0.5C0.223858 8 0 7.77614 0 7.5C0 7.22386 0.223858 7 0.5 7H4.13659C4.37835 5.35935 5.79206 4.1001 7.49989 4.1001C9.20772 4.1001 10.6214 5.35935 10.8632 7H14.5C14.7761 7 15 7.22386 15 7.5C15 7.77614 14.7761 8 14.5 8H10.8632Z"
fill="currentColor"
fillRule="evenodd"
clipRule="evenodd"
></path>
</svg>) to manually set the maximum number of rows to be returned.
</>)
}, 60_000);
}
export function getDatasFromKernelBySql(fieldMetas: any) {
return async (payload: IDataQueryPayload) => {
const sql = parser_dsl_with_meta(
"pygwalker_mid_table",
JSON.stringify({...payload, limit: payload.limit ?? DEFAULT_LIMIT}),
JSON.stringify({"pygwalker_mid_table": fieldMetas})
);
const result = await batchGetDatasBySql.getDatas(sql) ?? [];
if (!payload.limit && result.length === DEFAULT_LIMIT) {
notifyDataLimit();
}
return result as IRow[];
}
}
export async function getDatasFromKernelByPayload(payload: IDataQueryPayload) {
const result = await batchGetDatasByPayload.getDatas({...payload, limit: payload.limit ?? DEFAULT_LIMIT}) ?? [];
if (!payload.limit && result.length === DEFAULT_LIMIT) {
notifyDataLimit();
}
return result as IRow[];
}
================================================
FILE: app/src/index.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 10% 3.9%;
--radius: 0.5rem;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
}
}
================================================
FILE: app/src/index.tsx
================================================
import React, { useCallback, useContext, useEffect, useState } from 'react';
import { createRoot } from 'react-dom/client';
import { observer } from "mobx-react-lite";
import { reaction } from "mobx"
import { GraphicWalker, PureRenderer, GraphicRenderer, TableWalker } from '@kanaries/graphic-walker'
import type { VizSpecStore } from '@kanaries/graphic-walker/store/visualSpecStore'
import type { IGWHandler, IViewField, ISegmentKey, IDarkMode, IChatMessage, IRow } from '@kanaries/graphic-walker/interfaces';
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Streamlit, withStreamlitConnection } from "streamlit-component-lib"
import { createRender, useModel } from "@anywidget/react";
import Options from './components/options';
import { IAppProps } from './interfaces';
import { loadDataSource, postDataService, finishDataService, getDatasFromKernelBySql, getDatasFromKernelByPayload } from './dataSource';
import commonStore from "./store/common";
import { initJupyterCommunication, initHttpCommunication, streamlitComponentCallback, initAnywidgetCommunication } from "./utils/communication";
import communicationStore from "./store/communication"
import { setConfig } from './utils/userConfig';
import CodeExportModal from './components/codeExportModal';
import type { IPreviewProps, IChartPreviewProps } from './components/preview';
import { Preview, ChartPreview } from './components/preview';
import UploadSpecModal from "./components/uploadSpecModal"
import UploadChartModal from './components/uploadChartModal';
import InitModal from './components/initModal';
import { getSaveTool } from './tools/saveTool';
import { getExportTool } from './tools/exportTool';
import { getExportDataframeTool } from './tools/exportDataframe';
import { getRuncellTool } from './tools/runcellTool';
import { formatExportedChartDatas } from "./utils/save";
import { tracker } from "@/utils/tracker";
import Notification from "./notify"
import initDslParser from "@kanaries/gw-dsl-parser";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import {
ToggleGroup,
ToggleGroupItem,
} from "@/components/ui/toggle-group"
import { SunIcon, MoonIcon, DesktopIcon, ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons"
// @ts-ignore
import style from './index.css?inline'
import { currentMediaTheme } from './utils/theme';
import { AppContext, darkModeContext } from './store/context';
import FormatSpec from './utils/formatSpec';
import { getOpenDesktopTool } from './tools/openDesktop';
import RuncellBanner from './components/runcellBanner';
const initChart = async (gwRef: React.MutableRefObject<IGWHandler | null>, total: number, props: IAppProps) => {
if (props.needInitChart && props.env === "jupyter_widgets" && total !== 0) {
commonStore.setInitModalOpen(true);
commonStore.setInitModalInfo({
title: "Recover Charts",
curIndex: 0,
total: total,
});
for await (const chart of gwRef.current?.exportChartList("data-url")!) {
await communicationStore.comm?.sendMsg("save_chart", await formatExportedChartDatas(chart.data));
commonStore.setInitModalInfo({
title: "Recover Charts",
curIndex: chart.index + 1,
total: chart.total,
});
}
}
commonStore.setInitModalOpen(false);
}
const getComputationCallback = (props: IAppProps) => {
if (props.useKernelCalc && props.parseDslType === "client") {
return getDatasFromKernelBySql(props.fieldMetas);
}
if (props.useKernelCalc && props.parseDslType === "server") {
return getDatasFromKernelByPayload;
}
}
const MainApp = observer((props: {children: React.ReactNode, darkMode: "dark" | "light" | "media", hideToolBar?: boolean, gid?: string, sendMessage?: boolean}) => {
const [portal, setPortal] = useState<HTMLDivElement | null>(null);
const [selectedDarkMode, setSelectedDarkMode] = useState(props.darkMode);
const [darkMode, setDarkMode] = useState(currentMediaTheme(props.darkMode));
const sendAppearanceMessageToParent = useCallback((appearance: IDarkMode) => {
if (!props.sendMessage) return;
window.parent.postMessage({
action: "changeAppearance",
gid: props.gid,
appearance: appearance
}, "*");
}, [props.gid, props.sendMessage]);
useEffect(() => {
if (selectedDarkMode === "media") {
setDarkMode(currentMediaTheme(selectedDarkMode));
sendAppearanceMessageToParent(currentMediaTheme(selectedDarkMode));
const media = window.matchMedia('(prefers-color-scheme: dark)');
const listener = (e: MediaQueryListEvent) => {
setDarkMode(e.matches ? "dark" : "light");
}
media.addEventListener("change", listener);
return () => media.removeEventListener("change", listener);
} else {
sendAppearanceMessageToParent(selectedDarkMode);
setDarkMode(selectedDarkMode);
}
}, [selectedDarkMode])
return (
<AppContext
portalContainerContext={portal}
darkModeContext={darkMode}
>
<div className={`${darkMode === "dark" ? "dark": ""} bg-background text-foreground`}>
<div className="p-2">
<style>{style}</style>
<div className='overflow-y-auto'>
{ props.children }
</div>
{!props.hideToolBar && (
<div className="flex w-full mt-1 p-1 overflow-hidden border-t border-border">
<ToggleGroup
type="single"
value={selectedDarkMode}
onValueChange={(value) => {value && setSelectedDarkMode(value as IDarkMode)}}
>
<ToggleGroupItem value="dark">
<MoonIcon className="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="light">
<SunIcon className="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="media">
<DesktopIcon className="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
</div>
)}
<InitModal />
<div ref={setPortal}></div>
</div>
</div>
</AppContext>
)
})
const ExploreApp: React.FC<IAppProps & {initChartFlag: boolean}> = (props) => {
const gwRef = React.useRef<IGWHandler|null>(null);
const { userConfig } = props;
const [exportOpen, setExportOpen] = useState(false);
const [mode, setMode] = useState<string>("walker");
const [visSpec, setVisSpec] = useState(props.visSpec);
const [hideModeOption, _] = useState(true);
const [isChanged, setIsChanged] = useState(false);
const storeRef = React.useRef<VizSpecStore|null>(null);
const disposerRef = React.useRef<() => void>();
const storeRefProxied = React.useMemo(
() =>
new Proxy(storeRef, {
set(target, prop, value) {
if (prop === "current") {
if (value) {
disposerRef.current?.();
const store = value as VizSpecStore;
disposerRef.current = reaction(
() => store.currentVis,
() => {
setIsChanged((value as VizSpecStore).canUndo);
streamlitComponentCallback({
event: "spec_change",
data: store.exportCode()
});
},
);
}
}
return Reflect.set(target, prop, value);
},
}),
[]
);
commonStore.setVersion(props.version!);
useEffect(() => {
commonStore.setShowCloudTool(props.showCloudTool);
tracker.setUserId(props.hashcode ?? "");
if (userConfig) {
setConfig(userConfig);
tracker.setOpen(userConfig.privacy === "events");
};
}, []);
useEffect(() => {
if (props.initChartFlag) {
setTimeout(() => { initChart(gwRef, visSpec.length, props) }, 0);
}
}, [props.initChartFlag]);
useEffect(() => {
setTimeout(() => {
storeRef.current?.setSegmentKey(props.defaultTab as ISegmentKey);
}, 0);
}, [mode]);
const runcellTool = getRuncellTool();
const exportTool = getExportTool(setExportOpen);
const openInDesktopTool = getOpenDesktopTool(props, storeRef);
const tools = [runcellTool, exportTool, openInDesktopTool];
if (props.env && ["jupyter_widgets", "streamlit", "gradio", "marimo", "anywidget", "web_server"].indexOf(props.env) !== -1 && props.useSaveTool) {
const saveTool = getSaveTool(props, gwRef, storeRef, isChanged, setIsChanged);
tools.push(saveTool);
}
if (props.isExportDataFrame) {
const exportDataFrameTool = getExportDataframeTool(props, storeRef);
tools.push(exportDataFrameTool);
}
const toolbarConfig = {
exclude: ["export_code"],
extra: tools
}
const enhanceAPI = React.useMemo(() => {
if (props.showCloudTool) {
const features: Record<string, any> = {};
if (props.enableAskViz) {
features["askviz"] = async (metas: IViewField[], query: string) => {
const resp = await communicationStore.comm?.sendMsg("get_spec_by_text", { metas, query });
return resp?.data.data;
};
}
if (props.enableVlChat) {
features["vlChat"] = async (metas: IViewField[], chats: IChatMessage[]) => {
const resp = await communicationStore.comm?.sendMsg("get_chart_by_chats", { metas, chats });
return resp?.data.data;
};
}
if (Object.keys(features).length > 0) {
return { features };
}
}
return undefined;
}, [props.showCloudTool, props.enableAskViz, props.enableVlChat]);
const computationCallback = React.useMemo(() => getComputationCallback(props), []);
const modeChange = (value: string) => {
if (mode === "walker") {
setVisSpec(storeRef.current?.exportCode());
}
setMode(value);
}
return (
<React.StrictMode>
<Notification />
<UploadSpecModal storeRef={storeRef} setGwIsChanged={setIsChanged} />
<UploadChartModal gwRef={gwRef} storeRef={storeRef} dark={useContext(darkModeContext)} />
<CodeExportModal open={exportOpen} setOpen={setExportOpen} globalStore={storeRef} sourceCode={props["sourceInvokeCode"] || ""} />
{
!hideModeOption &&
<Select onValueChange={modeChange} defaultValue='walker' >
<SelectTrigger className="w-[140px] h-[30px] mb-[20px] text-xs">
<span className='text-muted-foreground'>Mode: </span>
<SelectValue className='' placeholder="Mode" />
</SelectTrigger>
<SelectContent>
<SelectItem value="walker">Walker</SelectItem>
<SelectItem value="renderer">Renderer</SelectItem>
</SelectContent>
</Select>
}
{
mode === "walker" ?
<GraphicWalker
{...props.extraConfig}
appearance={useContext(darkModeContext)}
vizThemeConfig={props.themeKey}
fieldkeyGuard={props.fieldkeyGuard}
fields={props.rawFields}
data={props.useKernelCalc ? undefined : props.dataSource}
storeRef={storeRefProxied}
ref={gwRef}
toolbar={toolbarConfig}
computation={computationCallback}
enhanceAPI={enhanceAPI}
chart={visSpec.length === 0 ? undefined : visSpec}
experimentalFeatures={{ computedField: props.useKernelCalc }}
defaultConfig={{ config: { timezoneDisplayOffset: 0 } }}
/> :
<GraphicRendererApp
{...props}
dataSource={props.dataSource}
visSpec={visSpec}
/>
}
<Options {...props} />
</React.StrictMode>
);
}
const PureRednererApp: React.FC<IAppProps> = observer((props) => {
const computationCallback = getComputationCallback(props);
const spec = props.visSpec[0];
const [expand, setExpand] = useState(false);
return (
<React.StrictMode>
<div className='flex'>
{
!expand && (<PureRenderer
{...props.extraConfig}
appearance={useContext(darkModeContext)}
vizThemeConfig={props.themeKey}
name={spec.name}
visualConfig={spec.config}
visualLayout={spec.layout}
visualState={spec.encodings}
type='remote'
computation={computationCallback!}
/>)
}
{
expand && commonStore.isStreamlitComponent && (
<div style={{minWidth: "96%"}}>
<GraphicWalker
{...props.extraConfig}
appearance={useContext(darkModeContext)}
vizThemeConfig={props.themeKey}
fieldkeyGuard={props.fieldkeyGuard}
fields={props.rawFields}
data={props.useKernelCalc ? undefined : props.dataSource}
computation={computationCallback}
chart={props.visSpec}
experimentalFeatures={{ computedField: props.useKernelCalc }}
defaultConfig={{ config: { timezoneDisplayOffset: 0 } }}
/>
</div>
)
}
{ commonStore.isStreamlitComponent && expand && ( <ChevronLeftIcon className='h-6 w-6 cursor-pointer border border-black-600 rounded-full'onClick={() => setExpand(false)}></ChevronLeftIcon> )}
{ commonStore.isStreamlitComponent && !expand && ( <ChevronRightIcon className='h-6 w-6 cursor-pointer border border-black-600 rounded-full'onClick={() => setExpand(true)}></ChevronRightIcon> )}
</div>
</React.StrictMode>
)
});
const initOnJupyter = async(props: IAppProps) => {
const comm = initJupyterCommunication(props.id);
comm.registerEndpoint("postData", postDataService);
comm.registerEndpoint("finishData", finishDataService);
communicationStore.setComm(comm);
if (props.needLoadLastSpec) {
const visSpecResp = await comm.sendMsg("get_latest_vis_spec", {});
props.visSpec = FormatSpec(visSpecResp["data"]["visSpec"], props.rawFields);
}
if (props.needLoadDatas) {
comm.sendMsgAsync("request_data", {}, null);
}
await initDslParser();
}
const initOnHttpCommunication = async(props: IAppProps) => {
const comm = await initHttpCommunication(props.id, props.communicationUrl);
communicationStore.setComm(comm);
if ((props.gwMode === "explore" || props.gwMode === "filter_renderer") && props.needLoadLastSpec) {
const visSpecResp = await comm.sendMsg("get_latest_vis_spec", {});
props.visSpec = visSpecResp["data"]["visSpec"];
}
await initDslParser();
}
const initOnAnywidgetCommunication = async(props: IAppProps, model: import("@anywidget/types").AnyModel) => {
const comm = await initAnywidgetCommunication(props.id, model);
communicationStore.setComm(comm);
if ((props.gwMode === "explore" || props.gwMode === "filter_renderer") && props.needLoadLastSpec) {
const visSpecResp = await comm.sendMsg("get_latest_vis_spec", {});
props.visSpec = visSpecResp["data"]["visSpec"];
}
await initDslParser();
}
const defaultInit = async(props: IAppProps) => {}
function GWalkerComponent(props: IAppProps) {
const [initChartFlag, setInitChartFlag] = useState(false);
const [dataSource, setDataSource] = useState<IRow[]>(props.dataSource);
useEffect(() => {
if (props.needLoadDatas) {
loadDataSource(props.dataSourceProps).then((data) => {
setDataSource(data);
setInitChartFlag(true);
commonStore.setInitModalOpen(false);
})
} else {
setInitChartFlag(true);
}
}, []);
return (
<React.StrictMode>
<RuncellBanner env={props.env} />
{ props.gwMode === "explore" && <ExploreApp {...props} dataSource={dataSource} initChartFlag={initChartFlag} /> }
{ props.gwMode === "renderer" && <PureRednererApp {...props} dataSource={dataSource} /> }
{ props.gwMode === "filter_renderer" && <GraphicRendererApp {...props} dataSource={dataSource} /> }
{ props.gwMode === "table" && <TableWalkerApp {...props} dataSource={dataSource} /> }
</React.StrictMode>
)
}
function GWalker(props: IAppProps, id: string) {
props.visSpec = FormatSpec(props.visSpec, props.rawFields);
let preRender = defaultInit;
switch(props.env) {
case "jupyter_widgets":
preRender = initOnJupyter;
break;
case "streamlit":
preRender = initOnHttpCommunication;
break;
case "gradio":
preRender = initOnHttpCommunication;
break;
case "web_server":
preRender = initOnHttpCommunication;
break;
default:
preRender = defaultInit;
}
preRender(props).then(() => {
const container = document.getElementById(id);
if (container) {
createRoot(container).render(
<MainApp darkMode={props.dark} gid={props.id} sendMessage={props.env?.startsWith("jupyter")}>
<GWalkerComponent {...props} />
</MainApp>
);
}
})
}
function PreviewApp(props: IPreviewProps, containerId: string) {
props.charts = FormatSpec(props.charts.map(chart => chart.visSpec), [])
.map((visSpec, index) => { return {...props.charts[index], visSpec} });
if (window.document.getElementById(`gwalker-${props.gid}`)) {
window.document.getElementById(containerId)?.remove();
}
const container = document.getElementById(containerId);
if (container) {
createRoot(container).render(
<MainApp darkMode={props.dark} hideToolBar>
<Preview {...props} />
</MainApp>
);
}
}
function ChartPreviewApp(props: IChartPreviewProps, id: string) {
props.visSpec = FormatSpec([props.visSpec], [])[0];
const container = document.getElementById(id);
if (container) {
createRoot(container).render(
<MainApp darkMode={props.dark} hideToolBar>
<ChartPreview {...props} />
</MainApp>
);
}
}
function GraphicRendererApp(props: IAppProps) {
const computationCallback = getComputationCallback(props);
const containerSize = props["containerSize"] ?? [null, null];
const globalProps = {
rawFields: props.rawFields,
containerStyle: {
height: containerSize[1] ? `${containerSize[1]-200}px` : "700px",
width: containerSize[0] ? `${containerSize[0]-20}px` : "60%"
},
themeKey:props.themeKey,
dark: useContext(darkModeContext),
}
return (
<React.StrictMode>
<Tabs defaultValue="0" className="w-full">
<div className="overflow-x-auto max-w-full">
<TabsList>
{props.visSpec.map((chart, index) => {
return <TabsTrigger key={index} value={index.toString()}>{chart.name}</TabsTrigger>
})}
</TabsList>
</div>
{props.visSpec.map((chart, index) => {
return <TabsContent key={index} value={index.toString()}>
{
props.useKernelCalc ?
<GraphicRenderer
{...globalProps}
computation={computationCallback!}
chart={[chart]}
/> :
<GraphicRenderer
{...globalProps}
data={props.dataSource!}
chart={[chart]}
/>
}
</TabsContent>
})}
</Tabs>
</React.StrictMode>
)
}
function TableWalkerApp(props: IAppProps) {
const computationCallback = getComputationCallback(props);
const globalProps = {
rawFields: props.rawFields,
themeKey: props.themeKey,
dark: useContext(darkModeContext)
}
return (
<React.StrictMode>
{
props.useKernelCalc ?
<TableWalker
{...globalProps}
computation={computationCallback!}
/> :
<TableWalker
{...globalProps}
data={props.dataSource}
/>
}
</React.StrictMode>
)
}
function SteamlitGWalkerApp(streamlitProps: any) {
const props = streamlitProps.args as IAppProps;
const [inited, setInited] = useState(false);
const container = React.useRef<HTMLDivElement>(null);
props.visSpec = FormatSpec(props.visSpec, props.rawFields);
useEffect(() => {
commonStore.setIsStreamlitComponent(true);
initOnHttpCommunication(props).then(() => {
setInited(true);
})
}, []);
useEffect(() => {
if (!container.current) return;
const resizeObserver = new ResizeObserver(() => {
Streamlit.setFrameHeight((container.current?.clientHeight ?? 0) + 20);
})
resizeObserver.observe(container.current);
return () => resizeObserver.disconnect();
}, [inited]);
return (
<React.StrictMode>
{inited && (
<div ref={container}>
<MainApp darkMode={props.dark}>
<GWalkerComponent {...props} />
</MainApp>
</div>
)}
</React.StrictMode>
);
};
const StreamlitGWalker = () => {
const StreamlitGWalkerComponent = withStreamlitConnection(SteamlitGWalkerApp);
const container = document.getElementById("root");
if (container) {
createRoot(container).render(
<React.StrictMode>
<StreamlitGWalkerComponent />
</React.StrictMode>
);
}
}
function AnywidgetGWalkerApp() {
const [inited, setInited] = useState(false);
const model = useModel();
const props = JSON.parse(model.get("props")) as IAppProps;
props.visSpec = FormatSpec(props.visSpec, props.rawFields);
useEffect(() => {
initOnAnywidgetCommunication(props, model).then(() => {
setInited(true);
})
}, []);
return (
<React.StrictMode>
{!inited && <div>Loading...</div>}
{inited && (
<MainApp darkMode={props.dark}>
<GWalkerComponent {...props} />
</MainApp>
)}
</React.StrictMode>
);
}
export default { GWalker, PreviewApp, ChartPreviewApp, StreamlitGWalker, render: createRender(AnywidgetGWalkerApp) }
================================================
FILE: app/src/interfaces/index.ts
================================================
import type { IRow, IMutField } from '@kanaries/graphic-walker/interfaces'
import type { IDarkMode, IThemeKey, IComputationFunction } from '@kanaries/graphic-walker/interfaces';
export interface IAppProps {
// graphic-walker props
fieldkeyGuard: boolean;
themeKey: IThemeKey;
dark: IDarkMode;
// pygwalker props
dataSource: IRow[];
rawFields: IMutField[];
id: string;
dataSourceProps: IDataSourceProps;
version?: string;
hashcode?: string;
visSpec: any;
userConfig?: IUserConfig;
env?: string;
needLoadDatas?: boolean;
specType: string;
showCloudTool: boolean;
enableAskViz: boolean;
enableVlChat: boolean;
needInitChart: boolean;
useKernelCalc: boolean;
useSaveTool: boolean;
parseDslType: "server" | "client";
communicationUrl: string;
gwMode: "explore" | "renderer" | "filter_renderer" | "table";
needLoadLastSpec: boolean;
extraConfig?: any;
fieldMetas: any;
isExportDataFrame: boolean;
defaultTab: "data" | "vis";
}
export interface IDataSourceProps {
tunnelId: string;
dataSourceId: string;
}
export interface IUserConfig {
[key: string]: any;
privacy: 'events' | 'update-only' | 'offline';
}
================================================
FILE: app/src/lib/dslToWorkflow.ts
================================================
import { chartToWorkflow } from "@kanaries/graphic-walker/utils/workflow";
export default function Transform(str: string) {
return JSON.stringify(chartToWorkflow(JSON.parse(str)));
}
================================================
FILE: app/src/lib/utils.ts
================================================
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
================================================
FILE: app/src/lib/vegaToDsl.ts
================================================
import { VegaliteMapper } from '@kanaries/graphic-walker/lib/vl2gw';
export default function Transform(str: string) {
const params = JSON.parse(str);
return JSON.stringify(
VegaliteMapper(...[params["vl"], params["allFields"], params["visId"], params["name"]])
);
}
================================================
FILE: app/src/notify/index.tsx
================================================
import { Fragment } from "react";
import { Transition } from "@headlessui/react";
import { CheckCircleIcon } from "@heroicons/react/24/outline";
import { XMarkIcon } from "@heroicons/react/20/solid";
import commonStore, { INotification } from "../store/common";
import { observer } from "mobx-react-lite";
function getNotificationIcon(type: INotification["type"]) {
switch (type) {
case "success":
return <CheckCircleIcon className="h-6 w-6 text-green-400" aria-hidden="true" />;
case "error":
return <XMarkIcon className="h-6 w-6 text-red-400" aria-hidden="true" />;
case "info":
return <CheckCircleIcon className="h-6 w-6 text-blue-400" aria-hidden="true" />;
case "warning":
return <CheckCircleIcon className="h-6 w-6 text-yellow-400" aria-hidden="true" />;
}
}
const Notification: React.FC = observer(() => {
return (
<div>
{commonStore.notification && (
<div aria-live="assertive" className="pointer-events-none fixed inset-0 flex items-end px-4 py-6 sm:items-start sm:p-6 z-[25539]">
<div className="flex w-full flex-col items-center space-y-4 sm:items-end">
{/* Notification panel, dynamically insert this into the live region when it needs to be displayed */}
<Transition
show={commonStore.notification != null}
as={Fragment}
enter="transform ease-out duration-300 transition"
enterFrom="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
enterTo="translate-y-0 opacity-100 sm:translate-x-0"
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="pointer-events-auto w-full max-w-md overflow-hidden rounded-lg bg-zinc-700 shadow-lg ring-1 ring-black ring-opacity-5">
<div className="p-4">
<div className="flex items-start">
<div className="flex-shrink-0">
{getNotificationIcon(commonStore.notification.type)}
</div>
<div className="ml-3 w-0 flex-1 pt-0.5">
<p className="text-sm font-medium text-gray-50">{commonStore.notification.title}</p>
<p className="mt-1 text-sm text-gray-300">{commonStore.notification.message}</p>
</div>
<div className="ml-4 flex flex-shrink-0">
<button
type="button"
className="inline-flex rounded-md bg-zinc-900 text-gray-400 hover:text-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
onClick={() => {
commonStore.setNotification(null);
}}
>
<span className="sr-only">Close</span>
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
</button>
</div>
</div>
</div>
</div>
</Transition>
</div>
</div>
)}
</div>
);
});
export default Notification;
================================================
FILE: app/src/store/common.ts
================================================
import { makeObservable, observable, action } from 'mobx';
import { ReactElement } from "react";
interface IInitModalInfo {
total: number;
curIndex: number;
title: string;
}
export interface INotification {
title: string;
message: string | ReactElement;
type: "success" | "error" | "info" | "warning";
}
class CommonStore {
_notifyTimeoutFunc = setTimeout(() => {}, 0);
initModalOpen: boolean = false;
initModalInfo: IInitModalInfo = {
total: 0,
curIndex: 0,
title: "",
};
showCloudTool: boolean = false;
version: string = "";
notification: INotification | null = null;
uploadSpecModalOpen: boolean = false;
uploadChartModalOpen: boolean = false;
isStreamlitComponent: boolean = false;
setInitModalOpen(value: boolean) {
this.initModalOpen = value;
}
setInitModalInfo(info: IInitModalInfo) {
this.initModalInfo = info;
}
setShowCloudTool(value: boolean) {
this.showCloudTool = value;
}
setVersion(value: string) {
this.version = value;
}
setNotification(value: INotification | null, timeout: number = 5_000) {
clearTimeout(this._notifyTimeoutFunc);
this.notification = value;
this._notifyTimeoutFunc = setTimeout(() => {
this.notification = null;
}, timeout);
}
setUploadSpecModalOpen(value: boolean) {
this.uploadSpecModalOpen = value;
}
setUploadChartModalOpen(value: boolean) {
this.uploadChartModalOpen = value;
}
setIsStreamlitComponent(value: boolean) {
this.isStreamlitComponent = value;
}
constructor() {
makeObservable(this, {
initModalOpen: observable,
initModalInfo: observable,
showCloudTool: observable,
version: observable,
notification: observable,
uploadSpecModalOpen: observable,
uploadChartModalOpen: observable,
isStreamlitComponent: observable,
setInitModalOpen: action,
setInitModalInfo: action,
setShowCloudTool: action,
setVersion: action,
setNotification: action,
setUploadSpecModalOpen: action,
setUploadChartModalOpen: action,
setIsStreamlitComponent: action
});
}
}
const commonStore = new CommonStore();
export default commonStore;
================================================
FILE: app/src/store/communication.ts
================================================
import { makeObservable, observable, action } from 'mobx';
import { ICommunication } from '../utils/communication';
class CommunicationStore {
comm: ICommunication | null = null;
setComm(comm: ICommunication) {
this.comm = comm;
}
constructor() {
makeObservable(this, {
comm: observable,
setComm: action,
});
}
}
const communicationStore = new CommunicationStore();
export default communicationStore;
================================================
FILE: app/src/store/context.ts
================================================
import { createContext } from 'react';
import { composeContext } from "@/utils/context";
export const portalContainerContext = createContext<HTMLDivElement | null>(null);
export const darkModeContext = createContext<"dark" | "light">("light");
export const AppContext = composeContext({ portalContainerContext, darkModeContext });
================================================
FILE: app/src/tools/exportDataframe.tsx
================================================
import React, { useState } from 'react';
import communicationStore from "../store/communication"
import commonStore from '../store/common';
import { tracker } from "@/utils/tracker";
import { DocumentArrowDownIcon } from '@heroicons/react/24/outline';
import { Loader2 } from "lucide-react"
import type { IAppProps } from '../interfaces';
import { parser_dsl_with_meta } from "@kanaries/gw-dsl-parser";
import type { ToolbarButtonItem } from "@kanaries/graphic-walker/components/toolbar/toolbar-button"
import type { VizSpecStore } from '@kanaries/graphic-walker/store/visualSpecStore'
export function getExportDataframeTool(
props: IAppProps,
storeRef: React.MutableRefObject<VizSpecStore | null>
) : ToolbarButtonItem {
const [exporting, setExporting] = useState(false);
const exportSuccess = () => {
commonStore.setNotification({
type: "success",
title: "Tips",
message: <>
<p className='py-1'>export success, created new dataframe: </p>
<p className='font-semibold py-1'>`walker.last_exported_dataframe`</p>
<p className='py-1'>if you forgot set variable walker, you can use</p>
<p className='font-semibold py-1'>`pyg.GlobalVarManager.last_exported_dataframe`</p>
<p className='py-1'>to get current exported dataframe</p>
</>
}, 20_000);
setTimeout(() => {
setExporting(false);
}, 500);
}
const onClick = async () => {
if (exporting) return;
setExporting(true);
tracker.track("click", {"entity": "export_dataframe_icon"});
try {
if (props.parseDslType === "server") {
await communicationStore.comm?.sendMsg("export_dataframe_by_payload", {
payload: {
workflow: storeRef.current?.workflow,
},
encodings: storeRef.current?.currentVis.encodings,
});
} else {
const sql = parser_dsl_with_meta(
"pygwalker_mid_table",
JSON.stringify({workflow: storeRef.current?.workflow}),
JSON.stringify({"pygwalker_mid_table": props.fieldMetas})
);
await communicationStore.comm?.sendMsg("export_dataframe_by_sql", {
sql: sql,
encodings: storeRef.current?.currentVis.encodings
});
}
exportSuccess();
} catch (_) {
setExporting(false);
}
}
return {
key: "export_dataframe",
label: "export_dataframe",
icon: (iconProps?: any) => {
return exporting ? <Loader2 className='animate-spin' /> : <DocumentArrowDownIcon {...iconProps} />
},
onClick: onClick,
}
}
================================================
FILE: app/src/tools/exportTool.tsx
================================================
import React from 'react';
import { tracker } from "@/utils/tracker";
import { CodeBracketSquareIcon } from '@heroicons/react/24/outline';
import type { ToolbarButtonItem } from "@kanaries/graphic-walker/components/toolbar/toolbar-button"
export function getExportTool(
setExportOpen: React.Dispatch<React.SetStateAction<boolean>>
) : ToolbarButtonItem {
const onClick = () => {
setExportOpen(true);
tracker.track("click", {"entity": "export_code_icon"});
}
return {
key: "export_pygwalker_code",
label: "export_code",
icon: (iconProps?: any) => <CodeBracketSquareIcon {...iconProps} />,
onClick
}
}
================================================
FILE: app/src/tools/openDesktop.tsx
================================================
import React from "react";
import { tracker } from "@/utils/tracker";
import { ComputerDesktopIcon } from "@heroicons/react/24/outline";
import type { ToolbarButtonItem } from "@kanaries/graphic-walker/components/toolbar/toolbar-button";
import { IAppProps } from "@/interfaces";
import { VizSpecStore } from "@kanaries/graphic-walker";
import communicationStore from "@/store/communication";
export function getOpenDesktopTool(props: IAppProps, storeRef: React.MutableRefObject<VizSpecStore | null>): ToolbarButtonItem {
const onClick = async () => {
tracker.track("click", { entity: "open_desktop_icon" });
await communicationStore.comm?.sendMsg("open_in_desktop", {
spec: JSON.parse(JSON.stringify(storeRef.current?.visList)),
fields: JSON.parse(JSON.stringify(storeRef.current?.meta)),
});
};
return {
key: "open_in_desktop",
label: "open_desktop",
icon: (iconProps?: any) => <ComputerDesktopIcon {...iconProps} />,
onClick,
};
}
================================================
FILE: app/src/tools/runcellTool.tsx
================================================
import React from "react";
import { tracker } from "@/utils/tracker";
import type { ToolbarButtonItem } from "@kanaries/graphic-walker/components/toolbar/toolbar-button";
const RUNCELL_LOGO_URL = "https://www.runcell.dev/runcell-logo.svg";
const RUNCELL_WEBSITE = "https://www.runcell.dev?utm_source=pygwalker";
export function getRuncellTool(): ToolbarButtonItem {
const onClick = () => {
tracker.track("click", { entity: "runcell_icon" });
window.open(RUNCELL_WEBSITE, "_blank");
};
return {
key: "runcell",
label: "AI Agent",
icon: (iconProps?: any) => (
<img
src={RUNCELL_LOGO_URL}
alt="Jupyter AI Agent"
style={{
width: iconProps?.width || 20,
height: iconProps?.height || 20,
}}
/>
),
onClick,
};
}
================================================
FILE: app/src/tools/saveTool.tsx
================================================
import React, { useEffect, useState, useMemo } from 'react';
import communicationStore from "../store/communication"
import commonStore from '../store/common';
import { formatExportedChartDatas } from "../utils/save"
import { checkUploadPrivacy } from '../utils/userConfig';
import { tracker } from "@/utils/tracker";
import { chartToWorkflow } from "@kanaries/graphic-walker"
import { DocumentTextIcon } from '@heroicons/react/24/outline';
import { Loader2 } from "lucide-react"
import type { IAppProps } from '../interfaces';
import { Button } from "@/components/ui/button"
import type { IGWHandler } from '@kanaries/graphic-walker/interfaces';
import type { ToolbarButtonItem } from "@kanaries/graphic-walker/components/toolbar/toolbar-button"
import type { VizSpecStore } from '@kanaries/graphic-walker/store/visualSpecStore'
function DocumentTextIconWithRedPoint(iconProps) {
return (
<div style={{position: "relative"}} >
<DocumentTextIcon {...iconProps} />
<div style={{position: "absolute", top: "-2px", right: "-2px", width: "4px", height: "4px", borderRadius: "50%", backgroundColor: "red"}}></div>
</div>
)
}
export function getSaveTool(
props: IAppProps,
gwRef: React.MutableRefObject<IGWHandler | null>,
storeRef: React.MutableRefObject<VizSpecStore | null>,
isChanged: boolean,
setIsChanged: React.Dispatch<React.SetStateAction<boolean>>
) : ToolbarButtonItem {
const [saving, setSaving] = useState(false);
const showUploadButton = useMemo(() => {
return checkUploadPrivacy() && commonStore.showCloudTool;
}, [commonStore.showCloudTool]);
const saveSuccess = () => {
commonStore.setNotification({
type: "success",
title: "Tips",
message: "save success.",
}, 4_000);
setTimeout(() => {
setSaving(false);
}, 500);
}
const onClick = async (where: string) => {
if (saving) return;
setSaving(true);
tracker.track("click", {"entity": `save_${where}`, "spec_type": props.specType})
// if exportChart is undefined, it means that the chart is not reload, so we think dont need to save.
if (gwRef.current?.exportChart === undefined) {
saveSuccess();
return;
}
let chartData = await gwRef.current?.exportChart!("data-url");
try {
const visSpec = storeRef.current?.exportCode();
if (visSpec === undefined) {
throw new Error("visSpec is undefined");
}
if (storeRef.current?.visIndex !== undefined) {
const currentChart = visSpec[storeRef.current?.visIndex];
if (currentChart.layout.size.mode === "auto") {
currentChart.layout.size.width = chartData.container()?.clientWidth || 320;
currentChart.layout.size.height = chartData.container()?.clientHeight || 200;
}
}
await communicationStore.comm?.sendMsg("update_spec", {
"visSpec": visSpec,
"chartData": await formatExportedChartDatas(chartData),
"workflowList": visSpec.map((spec) => chartToWorkflow(spec))
});
} finally {
setSaving(false);
}
if (["json_file", "json_ksf"].indexOf(props.specType) === -1) {
if (checkUploadPrivacy() && commonStore.showCloudTool) {
commonStore.setUploadSpecModalOpen(true);
} else {
commonStore.setNotification({
type: "warning",
title: "Tips",
message: "spec params is not 'json_file', save is not supported.",
}, 4_000);
}
} else {
setIsChanged(false);
saveSuccess();
}
}
const onClickUpload = () => {
commonStore.setUploadChartModalOpen(true);
tracker.track("click", {"entity": "save_icon_form_upload", "spec_type": props.specType});
}
useEffect(() => {
let locker = false;
document.addEventListener("keydown", (event) => {
if ((event.metaKey || event.ctrlKey) && event.key === 's') {
event.preventDefault();
if (locker) return;
locker = true;
onClick("from_keyboard").then(() => {
locker = false;
});
}
});
}, [])
return {
key: "save",
label: "save",
icon: (iconProps?: any) => {
if (saving) return <Loader2 className='animate-spin' />;
return isChanged ? <DocumentTextIconWithRedPoint {...iconProps} /> : <DocumentTextIcon {...iconProps} />
},
form: (
<div className='flex flex-col'>
<Button variant="ghost" aria-label="save spec" onClick={() => onClick("icon_form_save")}>
save spec
</Button>
{showUploadButton && (
<Button variant="ghost" aria-label="upload chart" onClick={onClickUpload}>
upload chart
</Button>
)}
</div>
),
onClick: () => onClick("icon"),
}
}
================================================
FILE: app/src/utils/communication.tsx
================================================
import { v4 as uuidv4 } from 'uuid';
import commonStore from '../store/common';
import { Streamlit } from "streamlit-component-lib"
interface IResponse {
data?: any;
message?: string;
code: number;
}
interface ICommunication {
sendMsg: (action: string, data: any, timeout?: number) => Promise<IResponse>;
registerEndpoint: (action: string, callback: (data: any) => any) => void;
sendMsgAsync: (action: string, data: any, rid: string | null) => void;
}
const getSignalName = (rid: string) => {
return `hacker-comm-pyg-done-signal-${rid}`;
}
const getCurrentJupyterEnv = () => {
const host = window.parent.location.host;
if (host === 'datalore.jetbrains.com') {
return 'datalore';
}
return "jupyter";
}
const raiseRequestError = (message: string, code: number) => {
let showMsg = (
<>{message}</>
)
switch (code) {
case 20001:
showMsg = (
<>
<p className="py-1">kanaries_api_key is not valid.</p>
<p className="py-1">Please set kanaries_api_key first.</p>
<p className="py-1">execute it in terminal:</p>
<p className="font-semibold px-1 py-1">`pygwalker login`</p>
</>
);
break;
case 20002:
showMsg = (<>
<p className="py-1">
The usage of cloud config has reached the upper limit.
If you want to use more cloud config files, please subscribe to different cloud packages according to your usage:
<a className="font-semibold px-1" href="https://kanaries.net/home/pygwalker" target='_blank'>
https://kanaries.net/home/pygwalker
</a>
</p>
</>)
break;
}
commonStore.setNotification({
type: "error",
title: "Error",
message: showMsg || "Unknown error",
}, 20_000);
}
const initJupyterCommunication = (gid: string) => {
const kernelTextCount = 5;
let curKernelTextIndex = 0;
const jupyterEnv = getCurrentJupyterEnv();
const document = window.parent.document;
const htmlText = document.getElementsByClassName(`hacker-comm-pyg-html-store-${gid}`)[0].childNodes[1] as HTMLInputElement;
const kernelTextList = Array.from({ length: kernelTextCount }, (_, index) => index).map(index => {
return document.getElementsByClassName(`hacker-comm-pyg-kernel-store-${gid}-${index}`)[0].childNodes[1] as HTMLInputElement;
})
const endpoints = new Map<string, (data: any) => any>();
const bufferMap = new Map<string, any>();
const fetchOnJupyter = (value: string) => {
const event = new Event("input", { bubbles: true })
const kernelText = kernelTextList[curKernelTextIndex];
kernelText.value = value;
kernelText.dispatchEvent(event);
curKernelTextIndex = (curKernelTextIndex + 1) % kernelTextCount;
}
const onMessage = (msg: string) => {
const data = JSON.parse(msg);
const action = data.action;
if (action === "finish_request") {
bufferMap.set(data.rid, data.data);
document.dispatchEvent(new CustomEvent(getSignalName(data.rid)));
return
}
const callback = endpoints.get(action);
if (callback) {
const resp = callback(data.data) ?? {};
sendMsgAsync("finish_request", resp, data.rid);
}
}
const sendMsg = async(action: string, data: any, timeout: number = 30_000) => {
const rid = uuidv4();
const promise = new Promise<any>((resolve, reject) => {
setTimeout(() => {
sendMsgAsync(action, data, rid);
}, 0);
const timer = setTimeout(() => {
raiseRequestError("communication timeout", 0);
reject(new Error("get result timeout"));
}, timeout);
document.addEventListener(getSignalName(rid), (_) => {
clearTimeout(timer);
const resp = bufferMap.get(rid);
if (resp.code !== 0) {
raiseRequestError(resp.message, resp.code);
reject(new Error(resp.message));
}
resolve(resp);
});
});
return promise;
}
const sendMsgAsync = (action: string, data: any, rid: string | null) => {
rid = rid ?? uuidv4();
fetchOnJupyter(JSON.stringify({ gid, rid, action, data }));
}
const registerEndpoint = (action: string, callback: (data: any) => any) => {
endpoints.set(action, callback);
}
if (jupyterEnv === "datalore") {
const kernel = (window.parent as any).Jupyter.notebook.kernel;
if (kernel.__pre_can_handle_message === undefined) {
kernel.__pre_can_handle_message = kernel._can_handle_message;
}
kernel._can_handle_message = (msg: any) => {
if (msg.msg_type === "comm_msg" && msg.content.data.method === "update") {
const pygMsgStr = msg.content.data.state.value;
try {
if (JSON.parse(pygMsgStr).gid === gid) {
onMessage(pygMsgStr);
}
} catch (_) {}
}
return kernel.__pre_can_handle_message(msg);
}
} else {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === "attributes") {
onMessage(htmlText.value)
}
})
})
observer.observe(htmlText, {
attributes: true,
attributeFilter: ["placeholder"],
})
}
return {
sendMsg,
registerEndpoint,
sendMsgAsync,
}
}
const getRealApiUrl = async(basePath: string, baseApiUrl: string) => {
if (basePath === "") {
return baseApiUrl;
}
const basePathPart = basePath.split("/");
const possibleBasePaths: string[] = [];
for (let i = basePathPart.length; i >= 0; i--) {
possibleBasePaths.push(basePathPart.slice(0, i).join("/"));
}
const possibleApiUrls = possibleBasePaths.slice(0, 2).map(path => `${path.length === 0 ? '' : '/'}${path}/${baseApiUrl}`);
return (await Promise.all(possibleApiUrls.map(async(url) => {
try {
const resp = await fetch(
url,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "ping", data: {} }),
}
)
const respJson = await resp.json();
if (respJson.code === 0) {
return url;
}
return null;
} catch {
return null;
}
}))).find(url => url !== null) as string;
}
const initHttpCommunication = async(gid: string, baseUrl: string) => {
// temporary solution in streamlit could
const domain = window.parent.document.location.host.split(".").slice(-2).join('.');
let url = "";
if (domain === "streamlit.app") {
url = `/~/+/_stcore/_pygwalker/comm/${gid}`
} else {
const basePath = window.parent.location.pathname.replace(/\/+$/, '').replace(/^\/+/, '');
url = await getRealApiUrl(basePath, `${baseUrl}/${gid}`);
}
url = "/" + url.replace(new RegExp(`/*`), "");
const sendMsg = async(action: string, data: any, timeout: number = 30_000) => {
const timer = setTimeout(() => {
raiseRequestError("communication timeout", 0);
throw(new Error("get result timeout"));
}, timeout);
try {
const resp = await sendMsgAsync(action, data);
if (resp.code !== 0) {
raiseRequestError(resp.message, resp.code);
throw new Error(resp.message);
}
return resp;
} finally {
clearTimeout(timer);
}
}
const sendMsgAsync = async(action: string, data: any) => {
const rid = uuidv4();
return await (await fetch(
url,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action, data, rid, gid }),
}
)).json();
}
const registerEndpoint = (_: string, __: (data: any) => any) => {}
return {
sendMsg,
registerEndpoint,
sendMsgAsync,
}
}
const streamlitComponentCallback = (data: any) => {
if (commonStore.isStreamlitComponent) {
Streamlit.setComponentValue(data);
}
}
const initAnywidgetCommunication = async(gid: string, model: import("@anywidget/types").AnyModel) => {
const bufferMap = new Map<string, any>();
const onMessage = (msg: string) => {
const data = JSON.parse(msg);
const action = data.action;
if (action === "finish_request") {
bufferMap.set(data.rid, data.data);
document.dispatchEvent(new CustomEvent(getSignalName(data.rid)));
return
}
}
model.on("msg:custom", msg => {
if (msg.type !== "pyg_response") {
return;
}
onMessage(msg.data);
});
const sendMsg = async(action: string, data: any, timeout: number = 30_000) => {
const rid = uuidv4();
const promise = new Promise<any>((resolve, reject) => {
setTimeout(() => {
sendMsgAsync(action, data, rid);
}, 0);
const timer = setTimeout(() => {
raiseRequestError("communication timeout", 0);
reject(new Error("get result timeout"));
}, timeout);
document.addEventListener(getSignalName(rid), (_) => {
clearTimeout(timer);
const resp = bufferMap.get(rid);
if (resp.code !== 0) {
raiseRequestError(resp.message, resp.code);
reject(new Error(resp.message));
}
resolve(resp);
});
});
return promise;
}
const sendMsgAsync = (action: string, data: any, rid: string | null) => {
rid = rid ?? uuidv4();
model.send({type: "pyg_request", msg: { gid, rid, action, data }});
}
const registerEndpoint = (_: string, __: (data: any) => any) => {}
return {
sendMsg,
registerEndpoint,
sendMsgAsync,
}
}
export type { ICommunication };
export { initJupyterCommunication, initHttpCommunication, streamlitComponentCallback, initAnywidgetCommunication };
================================================
FILE: app/src/utils/context.tsx
================================================
import React, { Context, ContextType } from 'react';
export function composeContext<T extends Record<string, Context<any>>>(contexts: T) {
return function (
props: { children?: React.ReactNode | Iterable<React.ReactNode> } & {
[K in keyof T]: ContextType<T[K]>;
}
) {
let node = props.children;
Object.keys(contexts).forEach((contextKey) => {
const context = contexts[contextKey];
node = <context.Provider value={props[contextKey]}>{node}</context.Provider>;
});
return node as JSX.Element;
};
}
================================================
FILE: app/src/utils/formatSpec.ts
================================================
import { VegaliteMapper } from '@kanaries/graphic-walker/lib/vl2gw';
export default function FormatSpec(spec: any[], fields: any[]) {
return spec.map((item, index) => {
if (["config", "encodings", "visId"].every(key => item.hasOwnProperty(key))) {
return item;
} else {
const result = VegaliteMapper(item, fields, Math.random().toString(16).split(".").at(1)!, `Chart ${index+1}`);
return result;
}
})
}
================================================
FILE: app/src/utils/save.ts
================================================
import * as htmlToImage from 'html-to-image';
import type { IChartExportResult } from '@kanaries/graphic-walker/interfaces';
export function download(data: string, filename: string, type: string) {
var file = new Blob([data], { type: type });
// @ts-ignore
if (window.navigator.msSaveOrOpenBlob)
// IE10+
// @ts-ignore
window.navigator.msSaveOrOpenBlob(file, filename);
else {
// Others
var a = document.createElement("a"),
url = URL.createObjectURL(file);
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(function () {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
}
export async function formatExportedChartDatas(chartData: IChartExportResult) {
const chartDom = chartData.container();
if (chartDom === null) {
return {
...chartData,
singleChart: ""
};
}
// export png don't support geo chart
if (chartData.charts.length === 0) {
return {
...chartData,
nCols: 1,
nRows: 1,
charts: [{
colIndex: 0,
rowIndex: 0,
width: chartDom?.clientWidth,
height: chartDom?.clientHeight,
canvasWidth: chartDom?.clientWidth,
canvasHeight: chartDom?.clientHeight,
data: "",
canvas: () => null
}],
singleChart: ""
}
} else {
const singleChart = await htmlToImage.toPng(
chartDom!,
{width: chartDom?.scrollWidth, height: chartDom?.scrollHeight}
)
return {
...chartData,
singleChart
}
}
}
export function getTimezoneOffsetSeconds(): number {
return -new Date().getTimezoneOffset() * 60;
}
================================================
FILE: app/src/utils/theme.ts
================================================
export function currentMediaTheme(dark: "dark" | "light" | "media"): "dark" | "light" {
if (dark === "media") {
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
gitextract__wqcb176/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── any-questions-topics-you-want-to-share.md
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows/
│ ├── auto-ci.yml
│ └── publish.yml
├── .gitignore
├── .gitmodules
├── .pylintrc
├── CITATION.cff
├── CONDUCT.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── app/
│ ├── .gitignore
│ ├── components.json
│ ├── index.html
│ ├── package.json
│ ├── postcss.config.js
│ ├── src/
│ │ ├── components/
│ │ │ ├── codeExportModal/
│ │ │ │ ├── index.tsx
│ │ │ │ └── usePythonCode.ts
│ │ │ ├── initModal/
│ │ │ │ └── index.tsx
│ │ │ ├── options.tsx
│ │ │ ├── preview/
│ │ │ │ └── index.tsx
│ │ │ ├── runcellBanner/
│ │ │ │ └── index.tsx
│ │ │ ├── ui/
│ │ │ │ ├── badge.tsx
│ │ │ │ ├── button.tsx
│ │ │ │ ├── checkbox.tsx
│ │ │ │ ├── dialog.tsx
│ │ │ │ ├── input.tsx
│ │ │ │ ├── label.tsx
│ │ │ │ ├── select.tsx
│ │ │ │ ├── tabs.tsx
│ │ │ │ ├── toggle-group.tsx
│ │ │ │ └── toggle.tsx
│ │ │ ├── uploadChartModal/
│ │ │ │ └── index.tsx
│ │ │ └── uploadSpecModal/
│ │ │ └── index.tsx
│ │ ├── dataSource/
│ │ │ └── index.tsx
│ │ ├── index.css
│ │ ├── index.tsx
│ │ ├── interfaces/
│ │ │ └── index.ts
│ │ ├── lib/
│ │ │ ├── dslToWorkflow.ts
│ │ │ ├── utils.ts
│ │ │ └── vegaToDsl.ts
│ │ ├── notify/
│ │ │ └── index.tsx
│ │ ├── store/
│ │ │ ├── common.ts
│ │ │ ├── communication.ts
│ │ │ └── context.ts
│ │ ├── tools/
│ │ │ ├── exportDataframe.tsx
│ │ │ ├── exportTool.tsx
│ │ │ ├── openDesktop.tsx
│ │ │ ├── runcellTool.tsx
│ │ │ └── saveTool.tsx
│ │ └── utils/
│ │ ├── communication.tsx
│ │ ├── context.tsx
│ │ ├── formatSpec.ts
│ │ ├── save.ts
│ │ ├── theme.ts
│ │ ├── tracker.ts
│ │ └── userConfig.ts
│ ├── tailwind.config.js
│ ├── tsconfig.json
│ └── vite.config.ts
├── bin/
│ └── pygwalker_command.py
├── docs/
│ ├── CONTRIBUTING.md
│ ├── DEVELOPMENT.md
│ ├── README.de.md
│ ├── README.es.md
│ ├── README.fr.md
│ ├── README.ja.md
│ ├── README.ko.md
│ ├── README.ru.md
│ ├── README.tr.md
│ └── README.zh.md
├── environment.yml
├── examples/
│ ├── README.md
│ ├── component_demo.ipynb
│ ├── dash_demo.py
│ ├── gradio_demo.py
│ ├── gw_config.json
│ ├── html_demo.py
│ ├── jupyter_demo.ipynb
│ ├── marimo_demo.py
│ ├── reflex_demo/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── __init__.py
│ │ ├── app/
│ │ │ ├── __init__.py
│ │ │ └── app.py
│ │ └── rxconfig.py
│ ├── streamlit_demo.py
│ └── web_server_demo.py
├── pygwalker/
│ ├── __init__.py
│ ├── _constants.py
│ ├── _typing.py
│ ├── api/
│ │ ├── __init__.py
│ │ ├── adapter.py
│ │ ├── anywidget.py
│ │ ├── component.py
│ │ ├── gradio.py
│ │ ├── html.py
│ │ ├── jupyter.py
│ │ ├── kanaries_cloud.py
│ │ ├── marimo.py
│ │ ├── pygwalker.py
│ │ ├── reflex.py
│ │ ├── streamlit.py
│ │ └── webserver.py
│ ├── communications/
│ │ ├── __init__.py
│ │ ├── anywidget_comm.py
│ │ ├── base.py
│ │ ├── gradio_comm.py
│ │ ├── hacker_comm.py
│ │ ├── reflex_comm.py
│ │ └── streamlit_comm.py
│ ├── data_parsers/
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── cloud_dataset_parser.py
│ │ ├── database_parser.py
│ │ ├── modin_parser.py
│ │ ├── pandas_parser.py
│ │ ├── polars_parser.py
│ │ └── spark_parser.py
│ ├── errors.py
│ ├── services/
│ │ ├── __init__.py
│ │ ├── check_update.py
│ │ ├── cloud_service.py
│ │ ├── config.py
│ │ ├── data_parsers.py
│ │ ├── fname_encodings.py
│ │ ├── format_invoke_walk_code.py
│ │ ├── global_var.py
│ │ ├── kaggle.py
│ │ ├── kanaries_cli_login.py
│ │ ├── preview_image.py
│ │ ├── render.py
│ │ ├── spec.py
│ │ ├── streamlit_components.py
│ │ ├── tip_tools.py
│ │ ├── track.py
│ │ └── upload_data.py
│ ├── templates/
│ │ ├── .gitignore
│ │ ├── index.html
│ │ ├── jupyter_iframe_message.html
│ │ ├── pygwalker_iframe.html
│ │ └── pygwalker_main_page.html
│ └── utils/
│ ├── __init__.py
│ ├── check_walker_params.py
│ ├── custom_sqlglot.py
│ ├── display.py
│ ├── dsl_transform.py
│ ├── encode.py
│ ├── estimate_tools.py
│ ├── execute_env_check.py
│ ├── free_port.py
│ ├── log.py
│ ├── payload_to_sql.py
│ ├── randoms.py
│ └── runtime_env.py
├── pygwalker_tools/
│ ├── __init__.py
│ └── metrics/
│ ├── __init__.py
│ ├── api.py
│ └── core.py
├── pyproject.toml
├── scripts/
│ ├── __init__.py
│ ├── ci_run_pytest.py
│ ├── compile.sh
│ ├── test-init.py
│ └── test-init.sh
├── tests/
│ ├── .gitignore
│ ├── __init__.py
│ ├── field-spec.ipynb
│ ├── main-modin.ipynb
│ ├── main-polars.ipynb
│ ├── main.ipynb
│ ├── test_component_api.ipynb
│ ├── test_data_parsers.py
│ ├── test_dsl_transform.py
│ ├── test_fname_encodings.py
│ └── test_format_invoke_walk_code.py
└── tutorials/
├── README.md
└── pygwalker_complete_tutorial.ipynb
SYMBOL INDEX (571 symbols across 98 files)
FILE: app/src/components/codeExportModal/index.tsx
type ICodeExport (line 22) | interface ICodeExport {
FILE: app/src/components/codeExportModal/usePythonCode.ts
function usePythonCode (line 5) | function usePythonCode (props: {
FILE: app/src/components/initModal/index.tsx
type IInitModal (line 7) | interface IInitModal {}
FILE: app/src/components/options.tsx
type ISolutionProps (line 8) | interface ISolutionProps {
constant RAND_HASH (line 57) | const RAND_HASH = Math.random().toString(16).split(".").at(1) + new Date...
FILE: app/src/components/preview/index.tsx
type IPreviewProps (line 10) | interface IPreviewProps {
type IChartPreviewProps (line 55) | interface IChartPreviewProps {
FILE: app/src/components/runcellBanner/index.tsx
constant RUNCELL_LOGO_URL (line 4) | const RUNCELL_LOGO_URL = "https://www.runcell.dev/runcell-logo.svg";
constant RUNCELL_WEBSITE (line 5) | const RUNCELL_WEBSITE = "https://www.runcell.dev?utm_source=pygwalker";
constant LLM_LOGOS (line 7) | const LLM_LOGOS = [
type RuncellBannerProps (line 14) | interface RuncellBannerProps {
FILE: app/src/components/ui/badge.tsx
type BadgeProps (line 26) | interface BadgeProps
function Badge (line 30) | function Badge({ className, variant, ...props }: BadgeProps) {
FILE: app/src/components/ui/button.tsx
type ButtonProps (line 37) | interface ButtonProps
FILE: app/src/components/ui/input.tsx
type InputProps (line 5) | interface InputProps
FILE: app/src/components/uploadChartModal/index.tsx
type IUploadChartModal (line 16) | interface IUploadChartModal {
FILE: app/src/components/uploadSpecModal/index.tsx
type IUploadSpecModal (line 16) | interface IUploadSpecModal {
FILE: app/src/dataSource/index.tsx
type MessagePayload (line 7) | interface MessagePayload extends IDataSourceProps {
type ICommPostDataMessage (line 16) | interface ICommPostDataMessage {
function loadDataSource (line 23) | async function loadDataSource(props: IDataSourceProps): Promise<IRow[]> {
function postDataService (line 58) | function postDataService(msg: ICommPostDataMessage) {
function finishDataService (line 71) | function finishDataService(msg: any) {
type IBatchGetDatasTask (line 81) | interface IBatchGetDatasTask {
function initBatchGetDatas (line 87) | function initBatchGetDatas(action: string) {
constant DEFAULT_LIMIT (line 125) | const DEFAULT_LIMIT = 50_000;
function notifyDataLimit (line 127) | function notifyDataLimit() {
function getDatasFromKernelBySql (line 147) | function getDatasFromKernelBySql(fieldMetas: any) {
function getDatasFromKernelByPayload (line 162) | async function getDatasFromKernelByPayload(payload: IDataQueryPayload) {
FILE: app/src/index.tsx
method set (line 167) | set(target, prop, value) {
function GWalkerComponent (line 394) | function GWalkerComponent(props: IAppProps) {
function GWalker (line 421) | function GWalker(props: IAppProps, id: string) {
function PreviewApp (line 453) | function PreviewApp(props: IPreviewProps, containerId: string) {
function ChartPreviewApp (line 471) | function ChartPreviewApp(props: IChartPreviewProps, id: string) {
function GraphicRendererApp (line 483) | function GraphicRendererApp(props: IAppProps) {
function TableWalkerApp (line 528) | function TableWalkerApp(props: IAppProps) {
function SteamlitGWalkerApp (line 554) | function SteamlitGWalkerApp(streamlitProps: any) {
function AnywidgetGWalkerApp (line 601) | function AnywidgetGWalkerApp() {
FILE: app/src/interfaces/index.ts
type IAppProps (line 4) | interface IAppProps {
type IDataSourceProps (line 37) | interface IDataSourceProps {
type IUserConfig (line 42) | interface IUserConfig {
FILE: app/src/lib/dslToWorkflow.ts
function Transform (line 3) | function Transform(str: string) {
FILE: app/src/lib/utils.ts
function cn (line 4) | function cn(...inputs: ClassValue[]) {
FILE: app/src/lib/vegaToDsl.ts
function Transform (line 3) | function Transform(str: string) {
FILE: app/src/notify/index.tsx
function getNotificationIcon (line 8) | function getNotificationIcon(type: INotification["type"]) {
FILE: app/src/store/common.ts
type IInitModalInfo (line 4) | interface IInitModalInfo {
type INotification (line 10) | interface INotification {
class CommonStore (line 16) | class CommonStore {
method setInitModalOpen (line 32) | setInitModalOpen(value: boolean) {
method setInitModalInfo (line 36) | setInitModalInfo(info: IInitModalInfo) {
method setShowCloudTool (line 40) | setShowCloudTool(value: boolean) {
method setVersion (line 44) | setVersion(value: string) {
method setNotification (line 48) | setNotification(value: INotification | null, timeout: number = 5_000) {
method setUploadSpecModalOpen (line 56) | setUploadSpecModalOpen(value: boolean) {
method setUploadChartModalOpen (line 60) | setUploadChartModalOpen(value: boolean) {
method setIsStreamlitComponent (line 64) | setIsStreamlitComponent(value: boolean) {
method constructor (line 68) | constructor() {
FILE: app/src/store/communication.ts
class CommunicationStore (line 4) | class CommunicationStore {
method setComm (line 7) | setComm(comm: ICommunication) {
method constructor (line 11) | constructor() {
FILE: app/src/tools/exportDataframe.tsx
function getExportDataframeTool (line 14) | function getExportDataframeTool(
FILE: app/src/tools/exportTool.tsx
function getExportTool (line 9) | function getExportTool(
FILE: app/src/tools/openDesktop.tsx
function getOpenDesktopTool (line 11) | function getOpenDesktopTool(props: IAppProps, storeRef: React.MutableRef...
FILE: app/src/tools/runcellTool.tsx
constant RUNCELL_LOGO_URL (line 6) | const RUNCELL_LOGO_URL = "https://www.runcell.dev/runcell-logo.svg";
constant RUNCELL_WEBSITE (line 7) | const RUNCELL_WEBSITE = "https://www.runcell.dev?utm_source=pygwalker";
function getRuncellTool (line 9) | function getRuncellTool(): ToolbarButtonItem {
FILE: app/src/tools/saveTool.tsx
function DocumentTextIconWithRedPoint (line 18) | function DocumentTextIconWithRedPoint(iconProps) {
function getSaveTool (line 27) | function getSaveTool(
FILE: app/src/utils/communication.tsx
type IResponse (line 5) | interface IResponse {
type ICommunication (line 11) | interface ICommunication {
FILE: app/src/utils/context.tsx
function composeContext (line 3) | function composeContext<T extends Record<string, Context<any>>>(contexts...
FILE: app/src/utils/formatSpec.ts
function FormatSpec (line 3) | function FormatSpec(spec: any[], fields: any[]) {
FILE: app/src/utils/save.ts
function download (line 4) | function download(data: string, filename: string, type: string) {
function formatExportedChartDatas (line 26) | async function formatExportedChartDatas(chartData: IChartExportResult) {
function getTimezoneOffsetSeconds (line 64) | function getTimezoneOffsetSeconds(): number {
FILE: app/src/utils/theme.ts
function currentMediaTheme (line 1) | function currentMediaTheme(dark: "dark" | "light" | "media"): "dark" | "...
FILE: app/src/utils/userConfig.ts
function checkUploadPrivacy (line 7) | function checkUploadPrivacy() {
function setConfig (line 11) | function setConfig(newConfig: IUserConfig) {
function getConfig (line 15) | function getConfig() {
FILE: bin/pygwalker_command.py
function command_set_config (line 76) | def command_set_config(value: Tuple[str]):
function command_reset_config (line 93) | def command_reset_config(value: Tuple[str]):
function command_reset_all_config (line 106) | def command_reset_all_config(_):
function command_list_config (line 114) | def command_list_config(_):
function main (line 124) | def main():
FILE: examples/marimo_demo.py
function __ (line 17) | def __(pd, walk):
function __ (line 24) | def __(pd, pyg):
function __ (line 31) | def __():
FILE: examples/reflex_demo/app/app.py
class State (line 42) | class State(rx.State):
function index (line 47) | def index() -> rx.Component:
function pygwalker_demo (line 55) | def pygwalker_demo() -> rx.Component:
function fallback_demo (line 89) | def fallback_demo() -> rx.Component:
function fallback_demo_with_error (line 133) | def fallback_demo_with_error(error_msg: str) -> rx.Component:
FILE: examples/streamlit_demo.py
function get_pyg_renderer (line 16) | def get_pyg_renderer() -> "StreamlitRenderer":
FILE: examples/web_server_demo.py
function init_pygwalker_entity_map (line 25) | def init_pygwalker_entity_map() -> Dict[str, PygWalker]:
function pygwalker_comm (line 59) | def pygwalker_comm(gid: str, payload: Dict[str, Any] = Body(...)):
function pyg_html (line 70) | def pyg_html(gid: str):
FILE: pygwalker/api/adapter.py
function walk (line 14) | def walk(
function render (line 86) | def render(
function table (line 133) | def table(
FILE: pygwalker/api/anywidget.py
class _WalkerWidget (line 18) | class _WalkerWidget(anywidget.AnyWidget):
function walk (line 24) | def walk(
FILE: pygwalker/api/component.py
function _convert_sql_to_field (line 26) | def _convert_sql_to_field(sql: str, is_agg_sql: bool) -> Dict[str, Any]:
function _convert_gw_agg_function_to_field (line 50) | def _convert_gw_agg_function_to_field(func_name: str, field_name: str) -...
function _convert_gw_bin_function_to_field (line 63) | def _convert_gw_bin_function_to_field(func_name: str, field_name: str, n...
function _handle_anonymous (line 85) | def _handle_anonymous(ast: exp.Anonymous, origin_str: str) -> Dict[str, ...
function _handle_agg_func (line 95) | def _handle_agg_func(ast: exp.AggFunc, origin_str: str) -> Dict[str, Any]:
class Expression (line 103) | class Expression(BaseModel):
class FieldInfo (line 109) | class FieldInfo(BaseModel):
class Component (line 120) | class Component:
method __init__ (line 130) | def __init__(
method copy (line 144) | def copy(self) -> "Component":
method _update_single_chart_spec (line 153) | def _update_single_chart_spec(self, key: str, value: Any) -> Dict[str,...
method _convert_string_to_field_info (line 161) | def _convert_string_to_field_info(self, s: str) -> Dict[str, Any]:
method _repr_html_ (line 183) | def _repr_html_(self) -> str:
method to_html (line 186) | def to_html(self) -> str:
method _get_single_chart_html (line 195) | def _get_single_chart_html(self) -> str:
method _get_explorer_html (line 198) | def _get_explorer_html(self) -> str:
method _get_profiling_html (line 207) | def _get_profiling_html(self) -> str:
method bar (line 215) | def bar(self) -> "Component":
method line (line 222) | def line(self) -> "Component":
method area (line 229) | def area(self) -> "Component":
method trail (line 236) | def trail(self) -> "Component":
method scatter (line 243) | def scatter(self) -> "Component":
method circle (line 250) | def circle(self) -> "Component":
method tick (line 257) | def tick(self) -> "Component":
method rect (line 264) | def rect(self) -> "Component":
method arc (line 271) | def arc(self) -> "Component":
method text (line 278) | def text(self) -> "Component":
method box (line 285) | def box(self) -> "Component":
method table (line 292) | def table(self) -> "Component":
method poi (line 299) | def poi(self) -> "Component":
method encode (line 307) | def encode(
method layout (line 360) | def layout(
method profiling (line 415) | def profiling(self) -> "Component":
method explorer (line 421) | def explorer(self) -> "Component":
function component (line 429) | def component(
FILE: pygwalker/api/gradio.py
function get_html_on_gradio (line 17) | def get_html_on_gradio(
FILE: pygwalker/api/html.py
function _to_html (line 18) | def _to_html(
function to_html (line 76) | def to_html(
function to_table_html (line 113) | def to_table_html(
function to_render_html (line 143) | def to_render_html(
function to_chart_html (line 172) | def to_chart_html(
FILE: pygwalker/api/jupyter.py
function walk (line 17) | def walk(
function render (line 102) | def render(
function table (line 147) | def table(
FILE: pygwalker/api/kanaries_cloud.py
function create_cloud_dataset (line 12) | def create_cloud_dataset(
function create_cloud_walker (line 41) | def create_cloud_walker(
function walk_on_cloud (line 78) | def walk_on_cloud(workspace_name: str, chart_name: str, kanaries_api_key...
FILE: pygwalker/api/marimo.py
class _WalkerWidget (line 19) | class _WalkerWidget(anywidget.AnyWidget):
function walk (line 25) | def walk(
FILE: pygwalker/api/pygwalker.py
class PygWalker (line 49) | class PygWalker:
method __init__ (line 51) | def __init__(
method last_exported_dataframe (line 120) | def last_exported_dataframe(self) -> Optional[pd.DataFrame]:
method _get_data_parser (line 123) | def _get_data_parser(
method _get_parse_dsl_type (line 153) | def _get_parse_dsl_type(self, data_parser: BaseDataParser) -> Literal[...
method _init_spec (line 160) | def _init_spec(self, spec: Dict[str, Any], field_specs: List[FieldSpec]):
method _update_vis_spec (line 171) | def _update_vis_spec(self, vis_spec: List[Dict[str, Any]]):
method _get_chart_map_dict (line 179) | def _get_chart_map_dict(self, chart_map: Dict[str, ChartData]) -> Dict...
method _parse_chart_map_dict (line 185) | def _parse_chart_map_dict(self, chart_map_dict: Dict[str, Any]) -> Dic...
method to_html (line 191) | def to_html(self, iframe_width: Optional[str] = None, iframe_height: O...
method to_html_without_iframe (line 195) | def to_html_without_iframe(self) -> str:
method display_on_convert_html (line 200) | def display_on_convert_html(self):
method display_on_jupyter (line 208) | def display_on_jupyter(self):
method display_on_jupyter_use_widgets (line 237) | def display_on_jupyter_use_widgets(self, iframe_width: Optional[str] =...
method display_preview_on_jupyter (line 264) | def display_preview_on_jupyter(self):
method chart_list (line 271) | def chart_list(self) -> List[str]:
method save_chart_to_file (line 277) | def save_chart_to_file(self, chart_name: str, path: str, save_type: Li...
method export_chart_html (line 299) | def export_chart_html(self, chart_name: str) -> str:
method export_chart_png (line 309) | def export_chart_png(self, chart_name: str) -> bytes:
method export_chart_svg (line 318) | def export_chart_svg(self, chart_name: str) -> bytes:
method display_chart (line 332) | def display_chart(self, chart_name: str, *, title: Optional[str] = Non...
method get_single_chart_html_by_spec (line 346) | def get_single_chart_html_by_spec(
method _get_chart_by_name (line 366) | def _get_chart_by_name(self, chart_name: str) -> ChartData:
method _init_callback (line 372) | def _init_callback(self, comm: BaseCommunication, preview_tool: Previe...
method _send_props_track (line 552) | def _send_props_track(self, props: Dict[str, Any]):
method _get_props (line 564) | def _get_props(
method _get_render_iframe (line 618) | def _get_render_iframe(
method _get_gw_preview_html (line 632) | def _get_gw_preview_html(self, manual: bool = False) -> str:
method _get_gw_chart_preview_html (line 654) | def _get_gw_chart_preview_html(self, chart_name: int, title: str, desc...
FILE: pygwalker/api/reflex.py
function get_component (line 19) | def get_component(
FILE: pygwalker/api/streamlit.py
class PreFilter (line 27) | class PreFilter(BaseModel):
function init_streamlit_comm (line 44) | def init_streamlit_comm():
class StreamlitRenderer (line 50) | class StreamlitRenderer:
method __init__ (line 52) | def __init__(
method _get_html_with_params_str_cache (line 121) | def _get_html_with_params_str_cache(self, params_str: str) -> str:
method _get_html (line 138) | def _get_html(
method _convert_pre_filters_to_gw_config (line 157) | def _convert_pre_filters_to_gw_config(
method set_global_pre_filters (line 187) | def set_global_pre_filters(self, pre_filters: List[PreFilter]):
method viewer (line 191) | def viewer(self, *, key: str = "viewer"):
method render_filter_renderer (line 197) | def render_filter_renderer(self, *args, **kwargs):
method explorer (line 200) | def explorer(
method render_explore (line 211) | def render_explore(self, *args, **kwargs):
method chart (line 214) | def chart(
method render_pure_chart (line 251) | def render_pure_chart(self, *args, **kwargs):
method table (line 254) | def table(
method _component (line 263) | def _component(
function get_streamlit_html (line 282) | def get_streamlit_html(
FILE: pygwalker/api/webserver.py
class _GlobalState (line 39) | class _GlobalState:
method __init__ (line 40) | def __init__(self, auto_shutdown: bool):
class _PygWalkerHandler (line 46) | class _PygWalkerHandler(http.server.SimpleHTTPRequestHandler):
method do_GET (line 50) | def do_GET(self):
method do_POST (line 73) | def do_POST(self):
method log_message (line 90) | def log_message(self, format, *args):
class CustomTCPServer (line 94) | class CustomTCPServer(socketserver.TCPServer):
method log_request (line 95) | def log_request(self, *args, **kwargs):
function _create_handler_with_walker (line 99) | def _create_handler_with_walker(walker: PygWalker, state: _GlobalState):
function _open_browser (line 108) | def _open_browser(address: str, delay_ms: int = 1000):
function _start_server (line 123) | def _start_server(
function walk (line 157) | def walk(
function render (line 222) | def render(
function table (line 274) | def table(
FILE: pygwalker/communications/anywidget_comm.py
class AnywidgetCommunication (line 11) | class AnywidgetCommunication(BaseCommunication):
method register_widget (line 13) | def register_widget(self, widget: anywidget.AnyWidget) -> None:
method send_msg_async (line 18) | def send_msg_async(self, action: str, data: Dict[str, Any], rid: Optio...
method _on_mesage (line 30) | def _on_mesage(self, _: anywidget.AnyWidget, data: Dict[str, Any], buf...
FILE: pygwalker/communications/base.py
function _upload_error_info (line 7) | def _upload_error_info(gid: str, action: str, error: Exception):
class BaseCommunication (line 19) | class BaseCommunication:
method __init__ (line 29) | def __init__(self, gid: str) -> None:
method send_msg_async (line 33) | def send_msg_async(self, action: str, data: Dict[str, Any]):
method _receive_msg (line 36) | def _receive_msg(self, action: str, data: Dict[str, Any]) -> Dict[str,...
method register (line 50) | def register(self, endpoint: str, func: Callable[[Dict[str, Any]], Any]):
FILE: pygwalker/communications/gradio_comm.py
function _pygwalker_router (line 17) | async def _pygwalker_router(req: Request) -> Response:
class GradioCommunication (line 32) | class GradioCommunication(BaseCommunication):
method __init__ (line 37) | def __init__(self, gid: str) -> None:
function _hack_gradio_server (line 50) | def _hack_gradio_server():
FILE: pygwalker/communications/hacker_comm.py
class HackerCommunication (line 13) | class HackerCommunication(BaseCommunication):
method __init__ (line 19) | def __init__(self, gid: str) -> None:
method send_msg_async (line 26) | def send_msg_async(self, action: str, data: Dict[str, Any], rid: Optio...
method get_widgets (line 47) | def get_widgets(self) -> Box:
method _on_mesage (line 53) | def _on_mesage(self, info: Dict[str, Any]):
method _get_html_widget (line 66) | def _get_html_widget(self) -> Text:
method _get_kernel_widget (line 71) | def _get_kernel_widget(self) -> List[Text]:
FILE: pygwalker/communications/reflex_comm.py
function _pygwalker_router (line 16) | async def _pygwalker_router(req: Request) -> Response:
class ReflexCommunication (line 44) | class ReflexCommunication(BaseCommunication):
method __init__ (line 47) | def __init__(self, gid: str) -> None:
function _create_pygwalker_app (line 53) | def _create_pygwalker_app() -> FastAPI:
function register_pygwalker_api (line 89) | def register_pygwalker_api(app: FastAPI) -> FastAPI:
FILE: pygwalker/communications/streamlit_comm.py
class PygwalkerHandler (line 24) | class PygwalkerHandler(tornado.web.RequestHandler):
method check_xsrf_cookie (line 28) | def check_xsrf_cookie(self):
method post (line 31) | def post(self, gid: str):
function hack_streamlit_server (line 47) | def hack_streamlit_server():
class StreamlitCommunication (line 62) | class StreamlitCommunication(BaseCommunication):
method __init__ (line 67) | def __init__(self, gid: str) -> None:
FILE: pygwalker/data_parsers/base.py
class FieldSpec (line 20) | class FieldSpec(BaseModel):
class BaseDataParser (line 38) | class BaseDataParser(abc.ABC):
method __init__ (line 42) | def __init__(
method raw_fields (line 54) | def raw_fields(self) -> List[Dict[str, str]]:
method to_records (line 59) | def to_records(self, limit: Optional[int] = None) -> List[Dict[str, An...
method get_datas_by_sql (line 64) | def get_datas_by_sql(self, sql: str) -> List[Dict[str, Any]]:
method get_datas_by_payload (line 69) | def get_datas_by_payload(self, payload: Dict[str, Any]) -> List[Dict[s...
method batch_get_datas_by_sql (line 74) | def batch_get_datas_by_sql(self, sql_list: List[str]) -> List[List[Dic...
method batch_get_datas_by_payload (line 79) | def batch_get_datas_by_payload(self, payload_list: List[Dict[str, Any]...
method to_csv (line 84) | def to_csv(self) -> io.BytesIO:
method to_parquet (line 89) | def to_parquet(self) -> io.BytesIO:
method dataset_type (line 95) | def dataset_type(self) -> str:
method field_metas (line 101) | def field_metas(self) -> List[Dict[str, str]]:
method placeholder_table_name (line 107) | def placeholder_table_name(self) -> str:
method data_size (line 113) | def data_size(self) -> int:
class BaseDataFrameDataParser (line 118) | class BaseDataFrameDataParser(Generic[DataFrame], BaseDataParser):
method __init__ (line 120) | def __init__(
method field_metas (line 138) | def field_metas(self) -> List[Dict[str, str]]:
method raw_fields (line 146) | def raw_fields(self) -> List[Dict[str, str]]:
method _infer_prop (line 152) | def _infer_prop(
method get_datas_by_sql (line 176) | def get_datas_by_sql(self, sql: str) -> List[Dict[str, Any]]:
method _rename_dataframe (line 191) | def _rename_dataframe(self, df: DataFrame) -> DataFrame:
method get_datas_by_payload (line 195) | def get_datas_by_payload(self, payload: Dict[str, Any]) -> List[Dict[s...
method batch_get_datas_by_sql (line 203) | def batch_get_datas_by_sql(self, sql_list: List[str]) -> List[List[Dic...
method batch_get_datas_by_payload (line 210) | def batch_get_datas_by_payload(self, payload_list: List[Dict[str, Any]...
method dataset_type (line 218) | def dataset_type(self) -> str:
method placeholder_table_name (line 222) | def placeholder_table_name(self) -> str:
method data_size (line 226) | def data_size(self) -> int:
function is_temporal_field (line 232) | def is_temporal_field(value: Any, infer_string_to_date: bool) -> bool:
function is_geo_field (line 244) | def is_geo_field(field_name: str) -> bool:
function format_temporal_string (line 253) | def format_temporal_string(value: str) -> str:
function get_data_meta_type (line 258) | def get_data_meta_type(data: Dict[str, Any]) -> List[Dict[str, str]]:
function get_timezone_base_offset (line 277) | def get_timezone_base_offset(offset_seconds: int) -> Optional[str]:
FILE: pygwalker/data_parsers/cloud_dataset_parser.py
class CloudDatasetParser (line 17) | class CloudDatasetParser(BaseDataParser):
method __init__ (line 19) | def __init__(
method _get_example_pandas_df (line 35) | def _get_example_pandas_df(self) -> pd.DataFrame:
method field_metas (line 45) | def field_metas(self) -> List[Dict[str, str]]:
method raw_fields (line 51) | def raw_fields(self) -> List[Dict[str, str]]:
method to_records (line 64) | def to_records(self, limit: Optional[int] = None) -> List[Dict[str, An...
method get_datas_by_payload (line 72) | def get_datas_by_payload(self, payload: Dict[str, Any]) -> List[Dict[s...
method get_datas_by_sql (line 76) | def get_datas_by_sql(self, sql: str) -> List[Dict[str, Any]]:
method to_csv (line 79) | def to_csv(self) -> io.BytesIO:
method to_parquet (line 84) | def to_parquet(self) -> io.BytesIO:
method _get_all_datas (line 89) | def _get_all_datas(self, limit: int) -> List[Dict[str, Any]]:
method batch_get_datas_by_sql (line 93) | def batch_get_datas_by_sql(self, sql_list: List[str]) -> List[List[Dic...
method batch_get_datas_by_payload (line 97) | def batch_get_datas_by_payload(self, payload_list: List[Dict[str, Any]...
method dataset_type (line 106) | def dataset_type(self) -> str:
method placeholder_table_name (line 110) | def placeholder_table_name(self) -> str:
method data_size (line 114) | def data_size(self) -> int:
FILE: pygwalker/data_parsers/database_parser.py
function _check_view_sql (line 24) | def _check_view_sql(sql: str) -> None:
class Connector (line 44) | class Connector:
method __init__ (line 61) | def __init__(self, url: str, view_sql: str, engine_params: Optional[Di...
method from_sqlalchemy_engine (line 68) | def from_sqlalchemy_engine(cls, engine: Engine, view_sql: str) -> "Con...
method from_sqlalchemy_connection (line 75) | def from_sqlalchemy_connection(cls, connection: Connection, view_sql: ...
method _init_instance (line 89) | def _init_instance(self, engine: Engine, view_sql: str):
method _get_or_create_engine (line 98) | def _get_or_create_engine(self, url: str, engine_params: Dict[str, Any...
method _run_pre_init_sql (line 106) | def _run_pre_init_sql(self, engine: Engine) -> None:
method query_datas (line 112) | def query_datas(self, sql: str) -> List[Dict[str, Any]]:
method dialect_name (line 136) | def dialect_name(self) -> str:
class DatabaseDataParser (line 140) | class DatabaseDataParser(BaseDataParser):
method __init__ (line 147) | def __init__(
method _get_example_pandas_df (line 162) | def _get_example_pandas_df(self) -> pd.DataFrame:
method _format_sql (line 170) | def _format_sql(self, sql: str) -> str:
method placeholder_table_name (line 186) | def placeholder_table_name(self) -> str:
method field_metas (line 191) | def field_metas(self) -> List[Dict[str, str]]:
method raw_fields (line 197) | def raw_fields(self) -> List[Dict[str, str]]:
method to_records (line 210) | def to_records(self, limit: Optional[int] = None) -> List[Dict[str, An...
method get_datas_by_payload (line 218) | def get_datas_by_payload(self, payload: Dict[str, Any]) -> List[Dict[s...
method get_datas_by_sql (line 228) | def get_datas_by_sql(self, sql: str) -> List[Dict[str, Any]]:
method _get_datas_by_sql (line 231) | def _get_datas_by_sql(self, sql: str) -> List[Dict[str, Any]]:
method to_csv (line 237) | def to_csv(self) -> io.BytesIO:
method to_parquet (line 242) | def to_parquet(self) -> io.BytesIO:
method batch_get_datas_by_sql (line 247) | def batch_get_datas_by_sql(self, sql_list: List[str]) -> List[List[Dic...
method batch_get_datas_by_payload (line 254) | def batch_get_datas_by_payload(self, payload_list: List[Dict[str, Any]...
method dataset_type (line 262) | def dataset_type(self) -> str:
method data_size (line 266) | def data_size(self) -> int:
FILE: pygwalker/data_parsers/modin_parser.py
class ModinPandasDataFrameDataParser (line 15) | class ModinPandasDataFrameDataParser(BaseDataFrameDataParser[mpd.DataFra...
method __init__ (line 17) | def __init__(
method to_records (line 28) | def to_records(self, limit: Optional[int] = None) -> List[Dict[str, An...
method to_csv (line 33) | def to_csv(self) -> io.BytesIO:
method to_parquet (line 38) | def to_parquet(self) -> io.BytesIO:
method _rename_dataframe (line 43) | def _rename_dataframe(self, df: mpd.DataFrame) -> mpd.DataFrame:
method _infer_semantic (line 48) | def _infer_semantic(self, s: mpd.Series, field_name: str):
method _infer_analytic (line 59) | def _infer_analytic(self, s: mpd.Series, field_name: str):
method dataset_type (line 74) | def dataset_type(self) -> str:
FILE: pygwalker/data_parsers/pandas_parser.py
class PandasDataFrameDataParser (line 14) | class PandasDataFrameDataParser(BaseDataFrameDataParser[pd.DataFrame]):
method to_records (line 17) | def to_records(self, limit: Optional[int] = None) -> List[Dict[str, An...
method to_csv (line 22) | def to_csv(self) -> io.BytesIO:
method to_parquet (line 27) | def to_parquet(self) -> io.BytesIO:
method _rename_dataframe (line 32) | def _rename_dataframe(self, df: pd.DataFrame) -> pd.DataFrame:
method _infer_semantic (line 37) | def _infer_semantic(self, s: pd.Series, field_name: str):
method _infer_analytic (line 48) | def _infer_analytic(self, s: pd.Series, field_name: str):
method dataset_type (line 63) | def dataset_type(self) -> str:
FILE: pygwalker/data_parsers/polars_parser.py
class PolarsDataFrameDataParser (line 14) | class PolarsDataFrameDataParser(BaseDataFrameDataParser[pl.DataFrame]):
method to_records (line 17) | def to_records(self, limit: Optional[int] = None) -> List[Dict[str, An...
method to_csv (line 22) | def to_csv(self) -> io.BytesIO:
method to_parquet (line 27) | def to_parquet(self) -> io.BytesIO:
method _rename_dataframe (line 32) | def _rename_dataframe(self, df: pl.DataFrame) -> pl.DataFrame:
method _infer_semantic (line 39) | def _infer_semantic(self, s: pl.Series, field_name: str):
method _infer_analytic (line 50) | def _infer_analytic(self, s: pl.Series, field_name: str):
method dataset_type (line 65) | def dataset_type(self) -> str:
FILE: pygwalker/data_parsers/spark_parser.py
class SparkDataFrameDataParser (line 18) | class SparkDataFrameDataParser(BaseDataParser):
method __init__ (line 20) | def __init__(
method raw_fields (line 45) | def raw_fields(self) -> List[Dict[str, str]]:
method field_metas (line 57) | def field_metas(self) -> List[Dict[str, str]]:
method to_records (line 61) | def to_records(self, limit: Optional[int] = None) -> List[Dict[str, An...
method get_datas_by_sql (line 65) | def get_datas_by_sql(self, sql: str) -> List[Dict[str, Any]]:
method get_datas_by_payload (line 71) | def get_datas_by_payload(self, payload: Dict[str, Any]) -> List[Dict[s...
method batch_get_datas_by_sql (line 79) | def batch_get_datas_by_sql(self, sql_list: List[str]) -> List[List[Dic...
method batch_get_datas_by_payload (line 86) | def batch_get_datas_by_payload(self, payload_list: List[Dict[str, Any]...
method to_csv (line 93) | def to_csv(self) -> io.BytesIO:
method to_parquet (line 98) | def to_parquet(self) -> io.BytesIO:
method _rename_dataframe (line 103) | def _rename_dataframe(self, df: DataFrame) -> DataFrame:
method dataset_type (line 108) | def dataset_type(self) -> str:
method placeholder_table_name (line 112) | def placeholder_table_name(self) -> str:
method data_size (line 116) | def data_size(self) -> int:
FILE: pygwalker/errors.py
class ErrorCode (line 7) | class ErrorCode(int, Enum):
class BaseError (line 14) | class BaseError(Exception):
method __init__ (line 16) | def __init__(self, *args, code: ErrorCode = ErrorCode.UNKNOWN_ERROR) -...
class InvalidConfigIdError (line 21) | class InvalidConfigIdError(BaseError):
class PrivacyError (line 26) | class PrivacyError(BaseError):
class CloudFunctionError (line 31) | class CloudFunctionError(BaseError):
class CsvFileTooLargeError (line 36) | class CsvFileTooLargeError(BaseError):
class ViewSqlSameColumnError (line 41) | class ViewSqlSameColumnError(BaseError):
class StreamlitPygwalkerApiError (line 46) | class StreamlitPygwalkerApiError(BaseError):
method __init__ (line 48) | def __init__(self) -> None:
FILE: pygwalker/services/check_update.py
function _sync_get_async_result (line 28) | def _sync_get_async_result(co: Coroutine[Any, Any, Any]) -> Any:
function _request_on_pyodide (line 45) | async def _request_on_pyodide(url: str) -> Dict[str, Any]:
function _request_on_python (line 63) | def _request_on_python(url: str) -> Dict[str, Any]:
function _check_update (line 80) | def _check_update() -> Dict[str, Any]:
function check_update (line 112) | def check_update() -> None:
FILE: pygwalker/services/cloud_service.py
function _get_database_type_from_dialect_name (line 19) | def _get_database_type_from_dialect_name(dialect_name: str) -> str:
function _upload_file_to_s3 (line 27) | def _upload_file_to_s3(url: str, content: io.BytesIO):
function _generate_chart_pre_redirect_uri (line 31) | def _generate_chart_pre_redirect_uri(chart_id: str, auth_code_info: Dict...
function read_config_from_cloud (line 43) | def read_config_from_cloud(path: str) -> str:
class PrivateSession (line 50) | class PrivateSession(requests.Session):
method __init__ (line 52) | def __init__(self, api_key: Optional[str]):
method prepare_request (line 56) | def prepare_request(self, request: requests.Request) -> requests.Prepa...
method send (line 61) | def send(self, request: requests.PreparedRequest, **kwargs) -> request...
class CloudService (line 93) | class CloudService:
method __init__ (line 95) | def __init__(self, api_key: str):
method _upload_file_dataset_meta (line 98) | def _upload_file_dataset_meta(
method _upload_dataset_callback (line 127) | def _upload_dataset_callback(self, dataset_id: str, fid_list: List[str...
method _create_chart (line 136) | def _create_chart(
method _create_notebook (line 161) | def _create_notebook(self, title: str, chart_id: str) -> Dict[str, Any]:
method _get_chart_by_name (line 175) | def _get_chart_by_name(self, name: str, workspace_name: str) -> Option...
method _get_auth_code_info (line 185) | def _get_auth_code_info(self) -> Dict[str, Any]:
method write_config_to_cloud (line 190) | def write_config_to_cloud(self, path: str, config: str):
method get_cloud_graphic_walker (line 198) | def get_cloud_graphic_walker(self, workspace_name: str, chart_name: st...
method create_cloud_graphic_walker (line 213) | def create_cloud_graphic_walker(
method get_kanaries_user_info (line 257) | def get_kanaries_user_info(self) -> Dict[str, Any]:
method get_spec_by_text (line 262) | def get_spec_by_text(self, metas: List[Dict[str, Any]], text: str) -> ...
method get_chart_by_chats (line 271) | def get_chart_by_chats(self, metas: List[Dict[str, Any]], chats: Any) ...
method create_file_dataset (line 280) | def create_file_dataset(
method create_datasource (line 295) | def create_datasource(
method get_datasource_by_name (line 313) | def get_datasource_by_name(self, name: str) -> Optional[str]:
method create_database_dataset (line 319) | def create_database_dataset(
method query_from_dataset (line 340) | def query_from_dataset(self, dataset_id: str, payload: Dict[str, Any])...
method batch_query_from_dataset (line 349) | def batch_query_from_dataset(self, dataset_id: str, query_list: List[D...
method create_cloud_dataset (line 357) | def create_cloud_dataset(
method create_dashboard (line 398) | def create_dashboard(
method upload_cloud_chart (line 419) | def upload_cloud_chart(
method upload_cloud_dashboard (line 463) | def upload_cloud_dashboard(
FILE: pygwalker/services/config.py
class ConfigItem (line 21) | class ConfigItem:
method __init__ (line 23) | def __init__(
method __str__ (line 35) | def __str__(self) -> str:
function get_config_params_help (line 62) | def get_config_params_help() -> str:
function _read_and_create_file (line 70) | def _read_and_create_file(path: str, default_content: Dict[str, str]) ->...
function set_config (line 84) | def set_config(new_config: Dict[str, str]):
function reset_config (line 99) | def reset_config(keys: List[str]):
function reset_all_config (line 116) | def reset_all_config():
function get_config (line 122) | def get_config(key: str) -> str:
function get_config_dict (line 136) | def get_config_dict() -> Dict[str, str]:
function get_all_config_str (line 141) | def get_all_config_str() -> str:
function get_local_user_id (line 147) | def get_local_user_id() -> str:
FILE: pygwalker/services/data_parsers.py
function _get_data_parser (line 17) | def _get_data_parser(dataset: Union[DataFrame, Connector, str]) -> Tuple...
function get_parser (line 64) | def get_parser(
function _get_pl_dataset_hash (line 87) | def _get_pl_dataset_hash(dataset: DataFrame) -> str:
function _get_pd_dataset_hash (line 98) | def _get_pd_dataset_hash(dataset: DataFrame) -> str:
function _get_modin_dataset_hash (line 108) | def _get_modin_dataset_hash(dataset: DataFrame) -> str:
function _get_spark_dataset_hash (line 120) | def _get_spark_dataset_hash(dataset: DataFrame) -> str:
function get_dataset_hash (line 132) | def get_dataset_hash(dataset: Union[DataFrame, Connector, str]) -> str:
FILE: pygwalker/services/fname_encodings.py
function base36encode (line 6) | def base36encode(s: str) -> str:
function base36decode (line 26) | def base36decode(s: str) -> str:
function fname_encode (line 32) | def fname_encode(fname: str) -> str:
function fname_decode (line 44) | def fname_decode(encode_fname: str) -> str:
function rename_columns (line 49) | def rename_columns(columns: List[str]) -> List[str]:
FILE: pygwalker/services/format_invoke_walk_code.py
function _find_walk_func_node (line 15) | def _find_walk_func_node(code: str) -> Optional['ast.Call']:
function _private_astor_pretty_source (line 36) | def _private_astor_pretty_source(source: List[str]) -> str:
function _repalce_spec_params_code (line 40) | def _repalce_spec_params_code(func: 'ast.Call') -> str:
function _get_default_code (line 54) | def _get_default_code() -> str:
function get_formated_spec_params_code (line 58) | def get_formated_spec_params_code(code: str) -> str:
function get_formated_spec_params_code_from_frame (line 65) | def get_formated_spec_params_code_from_frame(frame: FrameType) -> str:
FILE: pygwalker/services/global_var.py
class GlobalVarManager (line 9) | class GlobalVarManager:
method set_env (line 24) | def set_env(cls, env: Literal['Jupyter', 'Streamlit']):
method get_env (line 28) | def get_env(cls) -> Literal['Jupyter', 'Streamlit']:
method set_kanaries_api_key (line 32) | def set_kanaries_api_key(cls, api_key: str):
method set_kanaries_api_host (line 36) | def set_kanaries_api_host(cls, api_host: str):
method set_kanaries_main_host (line 40) | def set_kanaries_main_host(cls, main_host: str):
method set_privacy (line 44) | def set_privacy(cls, privacy: Literal['offline', 'update-only', 'event...
method set_last_exported_dataframe (line 48) | def set_last_exported_dataframe(cls, df: DataFrame):
method set_max_data_length (line 53) | def set_max_data_length(cls, length: int):
method set_component_url (line 57) | def set_component_url(cls, url: str):
FILE: pygwalker/services/kaggle.py
function auto_set_kanaries_api_key_on_kaggle (line 9) | def auto_set_kanaries_api_key_on_kaggle():
function adjust_kaggle_default_font_size (line 22) | def adjust_kaggle_default_font_size():
function show_tips_user_kaggle (line 27) | def show_tips_user_kaggle() -> bool:
FILE: pygwalker/services/kanaries_cli_login.py
class TextStyle (line 15) | class TextStyle:
class _CallbackHandler (line 22) | class _CallbackHandler(BaseHTTPRequestHandler):
method log_message (line 25) | def log_message(self, _, *args: Any) -> None:
method do_GET (line 28) | def do_GET(self):
function _run_callback_server (line 50) | def _run_callback_server(port: int):
function kanaries_login (line 56) | def kanaries_login():
FILE: pygwalker/services/preview_image.py
class ImgData (line 16) | class ImgData(BaseModel):
class ChartData (line 26) | class ChartData(BaseModel):
function render_gw_preview_html (line 34) | def render_gw_preview_html(
function render_gw_chart_preview_html (line 70) | def render_gw_chart_preview_html(
class PreviewImageTool (line 106) | class PreviewImageTool:
method __init__ (line 108) | def __init__(self, gid: str):
method init_display (line 117) | def init_display(self):
method render_gw_review (line 120) | def render_gw_review(self, html: str):
method async_render_gw_review (line 129) | def async_render_gw_review(self, html: str):
FILE: pygwalker/services/render.py
function compress_data (line 22) | def compress_data(data: str) -> str:
function get_max_limited_datas (line 34) | def get_max_limited_datas(datas: List[Dict[str, Any]], byte_limit: int) ...
function render_iframe_messages_html (line 43) | def render_iframe_messages_html(gid: str) -> str:
function render_gwalker_iframe (line 47) | def render_gwalker_iframe(
function render_gwalker_html (line 69) | def render_gwalker_html(gid: int, props: Dict[str, Any]) -> str:
FILE: pygwalker/services/spec.py
function _is_json (line 15) | def _is_json(s: str) -> bool:
function _get_spec_from_server (line 23) | def _get_spec_from_server(config_id: str) -> str:
function _get_spec_from_url (line 34) | def _get_spec_from_url(url: str) -> str:
function _get_spec_from_local (line 39) | def _get_spec_from_local(path: str) -> str:
function _is_config_id (line 44) | def _is_config_id(config_id: str) -> bool:
function _get_spec_json_from_diff_source (line 55) | def _get_spec_json_from_diff_source(spec: str) -> Tuple[str, str]:
function _config_adapter (line 89) | def _config_adapter(config: str) -> str:
function fill_new_fields (line 110) | def fill_new_fields(config: List[Dict[str, Any]], all_fields: List[Dict[...
function _config_adapter_045a5 (line 137) | def _config_adapter_045a5(config: List[Dict[str, Any]]):
function _is_gw_config (line 155) | def _is_gw_config(config: Dict[str, Any]) -> bool:
function _is_pygwalker_config (line 159) | def _is_pygwalker_config(config: Dict[str, Any]) -> bool:
function get_spec_json (line 163) | def get_spec_json(spec: Union[str, List[Any], Dict[str, Any]]) -> Tuple[...
FILE: pygwalker/services/streamlit_components.py
function pygwalker_component (line 13) | def pygwalker_component(props: Dict[str, Any], key: str) -> CustomCompon...
FILE: pygwalker/services/tip_tools.py
class TipOnStartTool (line 17) | class TipOnStartTool:
method __init__ (line 20) | def __init__(self, gid: str, tip_name: str):
method show (line 26) | def show(self):
method hide (line 29) | def hide(self):
FILE: pygwalker/services/track.py
function track_event (line 17) | def track_event(event: str, properties: Optional[Dict[str, Any]] = None):
FILE: pygwalker/services/upload_data.py
function _send_js (line 13) | def _send_js(js_code: str, slot_id: str):
function _send_upload_data_msg (line 20) | def _send_upload_data_msg(gid: int, msg: Dict[str, Any], slot_id: str):
function _rand_slot_id (line 30) | def _rand_slot_id():
class BatchUploadDatasToolOnJupyter (line 34) | class BatchUploadDatasToolOnJupyter:
method run (line 36) | def run(
class BatchUploadDatasToolOnWidgets (line 77) | class BatchUploadDatasToolOnWidgets:
method __init__ (line 79) | def __init__(self, comm: BaseCommunication) -> None:
method run (line 82) | def run(
FILE: pygwalker/utils/__init__.py
function fallback_value (line 2) | def fallback_value(*values):
FILE: pygwalker/utils/check_walker_params.py
function check_expired_params (line 7) | def check_expired_params(params: Dict[str, Any]):
FILE: pygwalker/utils/custom_sqlglot.py
function _postgres_round_generator (line 24) | def _postgres_round_generator(e: exp.Round) -> str:
function _postgres_unix_to_time_sql (line 30) | def _postgres_unix_to_time_sql(self: Generator, expression: exp.UnixToTi...
function _postgres_in_sql (line 41) | def _postgres_in_sql(self: Generator, expression: exp.In) -> str:
function _postgres_timestamp_trunc (line 52) | def _postgres_timestamp_trunc(self: Generator, expression: exp.Timestamp...
function _postgres_time_to_str_sql (line 59) | def _postgres_time_to_str_sql(self: Generator, expression: exp.TimeToStr...
function _postgres_str_to_time_sql (line 80) | def _postgres_str_to_time_sql(self: Generator, expression: exp.StrToTime...
function _postgres_regexp_like_sql (line 93) | def _postgres_regexp_like_sql(self: Generator, expression: exp.RegexpLik...
function _mysql_timestamptrunc_sql (line 110) | def _mysql_timestamptrunc_sql(self: Generator, expression: exp.Timestamp...
function _mysql_extract_sql (line 125) | def _mysql_extract_sql(self: Generator, expression: exp.Extract) -> str:
function _mysql_unix_to_time_sql (line 141) | def _mysql_unix_to_time_sql(self: Generator, expression: exp.UnixToTime)...
function _mysql_str_to_time_sql (line 148) | def _mysql_str_to_time_sql(self: Generator, expression: exp.StrToTime) -...
function _snowflake_extract_sql (line 164) | def _snowflake_extract_sql(self: Generator, expression: exp.Extract) -> ...
function _snowflake_time_to_str (line 177) | def _snowflake_time_to_str(self: Generator, expression: exp.TimeToStr) -...
function _snowflake_str_to_time_sql (line 194) | def _snowflake_str_to_time_sql(self: Generator, expression: exp.StrToTim...
function _snowflake_timestamp_trunc_sql (line 201) | def _snowflake_timestamp_trunc_sql(self: Generator, expression: exp.Time...
FILE: pygwalker/utils/display.py
function display_html (line 9) | def display_html(
FILE: pygwalker/utils/dsl_transform.py
function _make_js_callable (line 18) | def _make_js_callable(func_name, js_code):
function _ensure_js_runtime (line 34) | def _ensure_js_runtime():
function dsl_to_workflow (line 53) | def dsl_to_workflow(dsl: Dict[str, Any]) -> Dict[str, Any]:
function vega_to_dsl (line 58) | def vega_to_dsl(vega_config: Dict[str, Any], fields: List[Dict[str, Any]...
FILE: pygwalker/utils/encode.py
class DataFrameEncoder (line 8) | class DataFrameEncoder(json.JSONEncoder):
method default (line 10) | def default(self, o):
FILE: pygwalker/utils/estimate_tools.py
function estimate_average_data_size (line 7) | def estimate_average_data_size(datas: List[Dict[str, Any]]) -> int:
FILE: pygwalker/utils/execute_env_check.py
function check_convert (line 8) | def check_convert() -> bool:
function check_kaggle (line 21) | def check_kaggle() -> bool:
function get_kaggle_run_type (line 26) | def get_kaggle_run_type() -> Literal["batch", "interactive"]:
FILE: pygwalker/utils/free_port.py
function find_free_port (line 4) | def find_free_port() -> int:
FILE: pygwalker/utils/log.py
function init_logging (line 4) | def init_logging():
FILE: pygwalker/utils/payload_to_sql.py
function get_sql_from_payload (line 4) | def get_sql_from_payload(
FILE: pygwalker/utils/randoms.py
function rand_str (line 6) | def rand_str(n: int = 8, options: str = string.ascii_letters + string.di...
function generate_hash_code (line 10) | def generate_hash_code() -> str:
FILE: pygwalker/utils/runtime_env.py
function _is_jupyter (line 4) | def _is_jupyter() -> bool:
function get_current_env (line 15) | def get_current_env() -> Literal["jupyter", "other"]:
FILE: pygwalker_tools/metrics/api.py
class Chart (line 17) | class Chart:
method __init__ (line 19) | def __init__(self, data: DataFrame, spec: Dict[str, Any]):
method html (line 27) | def html(self) -> str:
method __str__ (line 30) | def __str__(self) -> str:
method _repr_html_ (line 33) | def _repr_html_(self):
class _JSONEncoder (line 37) | class _JSONEncoder(json.JSONEncoder):
method default (line 39) | def default(self, o):
function get_metrics_datas (line 48) | def get_metrics_datas(
class MetricsChart (line 126) | class MetricsChart:
method __init__ (line 171) | def __init__(
method _get_datas (line 183) | def _get_datas(self, metrics_name: str, params: Optional[Dict[str, Any...
method _format_encode (line 192) | def _format_encode(self, encode_params: Dict[str, Any]) -> Dict[str, A...
method pv (line 197) | def pv(self) -> Chart:
method uv (line 208) | def uv(self) -> Chart:
method mau (line 219) | def mau(self) -> Chart:
method retention (line 230) | def retention(self) -> Chart:
method new_user_count (line 241) | def new_user_count(self) -> Chart:
method cohort_matrix (line 252) | def cohort_matrix(self) -> Chart:
method active_user_count (line 270) | def active_user_count(self) -> Chart:
method user_churn_rate_base_active (line 281) | def user_churn_rate_base_active(self):
FILE: pygwalker_tools/metrics/core.py
function _replace_table_name_to_subquery (line 177) | def _replace_table_name_to_subquery(
function get_metrics_sql (line 208) | def get_metrics_sql(
function get_help_text (line 259) | def get_help_text() -> str:
FILE: scripts/ci_run_pytest.py
function _terminate_process (line 14) | def _terminate_process(proc: subprocess.Popen) -> None:
function _junit_all_passed (line 23) | def _junit_all_passed(junit_path: Path) -> bool:
function main (line 51) | def main() -> int:
FILE: tests/test_data_parsers.py
function test_data_parser_on_padnas (line 32) | def test_data_parser_on_padnas():
function test_data_parser_on_polars (line 42) | def test_data_parser_on_polars():
function test_data_parser_on_modin (line 54) | def test_data_parser_on_modin():
function test_check_view_sql (line 66) | def test_check_view_sql():
function test_connector (line 81) | def test_connector():
FILE: tests/test_dsl_transform.py
function _reset_runtime (line 10) | def _reset_runtime():
function test_dsl_to_workflow_returns_valid_workflow (line 16) | def test_dsl_to_workflow_returns_valid_workflow():
function test_dsl_to_workflow_with_fields (line 23) | def test_dsl_to_workflow_with_fields():
function test_vega_to_dsl_returns_expected_keys (line 35) | def test_vega_to_dsl_returns_expected_keys():
function test_import_error_when_no_js_runtime (line 53) | def test_import_error_when_no_js_runtime():
function test_runtime_initialized_only_once (line 67) | def test_runtime_initialized_only_once():
FILE: tests/test_fname_encodings.py
function test_base36_encode (line 9) | def test_base36_encode():
function test_base36_decode (line 15) | def test_base36_decode():
function test_fname_encode (line 21) | def test_fname_encode():
function test_fname_decode (line 27) | def test_fname_decode():
FILE: tests/test_format_invoke_walk_code.py
function test_get_formated_spec_params_code (line 4) | def test_get_formated_spec_params_code():
Condensed preview — 180 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (818K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/any-questions-topics-you-want-to-share.md",
"chars": 160,
"preview": "---\nname: Any Questions/Topics you want to share\nabout: Describe anything you want to talk about related to pygwalker\nti"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 625,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"[BUG] pygwalker bug report\"\nlabels: bug\nassignees"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 595,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
},
{
"path": ".github/workflows/auto-ci.yml",
"chars": 3324,
"preview": "# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tes"
},
{
"path": ".github/workflows/publish.yml",
"chars": 2646,
"preview": "name: Publish PyPI\n\non:\n workflow_dispatch:\n\njobs:\n build-js:\n\n runs-on: ubuntu-latest\n\n strategy:\n matrix:"
},
{
"path": ".gitignore",
"chars": 2062,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
},
{
"path": ".gitmodules",
"chars": 0,
"preview": ""
},
{
"path": ".pylintrc",
"chars": 14025,
"preview": "# This Pylint rcfile contains a best-effort configuration to uphold the\n# best-practices and style described in the Goog"
},
{
"path": "CITATION.cff",
"chars": 858,
"preview": "# This CITATION.cff file was generated with cffinit.\n# Visit https://bit.ly/cffinit to generate yours today!\n\ncff-versio"
},
{
"path": "CONDUCT.md",
"chars": 4811,
"preview": "# Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our community"
},
{
"path": "LICENSE",
"chars": 11353,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "MANIFEST.in",
"chars": 62,
"preview": "include LICENSE\ninclude README.md\ngraft pygwalker/templates/**"
},
{
"path": "README.md",
"chars": 20637,
"preview": "[English](README.md) | [Español](./docs/README.es.md) | [Français](./docs/README.fr.md) | [Deutsch](./docs/README.de.md)"
},
{
"path": "app/.gitignore",
"chars": 18,
"preview": "node_modules/\n!lib"
},
{
"path": "app/components.json",
"chars": 322,
"preview": "{\n \"$schema\": \"https://ui.shadcn.com/schema.json\",\n \"style\": \"new-york\",\n \"rsc\": false,\n \"tsx\": true,\n \"tailwind\": "
},
{
"path": "app/index.html",
"chars": 612,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=ed"
},
{
"path": "app/package.json",
"chars": 2436,
"preview": "{\n \"name\": \"pygwalker-app\",\n \"version\": \"0.0.1\",\n \"main\": \"index.ts\",\n \"license\": \"Apache License 2.0\",\n \"private\":"
},
{
"path": "app/postcss.config.js",
"chars": 82,
"preview": "module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n}\n"
},
{
"path": "app/src/components/codeExportModal/index.tsx",
"chars": 5777,
"preview": "import React, { useEffect, useState, useCallback } from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport { Li"
},
{
"path": "app/src/components/codeExportModal/usePythonCode.ts",
"chars": 848,
"preview": "import { chartToWorkflow } from \"@kanaries/graphic-walker/utils/workflow\";\nimport type { IChart } from \"@kanaries/graphi"
},
{
"path": "app/src/components/initModal/index.tsx",
"chars": 1377,
"preview": "import React from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport { Dialog, DialogContent, DialogTitle } fro"
},
{
"path": "app/src/components/options.tsx",
"chars": 8116,
"preview": "import React, { useEffect, useState } from \"react\";\nimport type { IAppProps } from \"../interfaces\";\n\nconst copyToClipboa"
},
{
"path": "app/src/components/preview/index.tsx",
"chars": 3267,
"preview": "import React from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport { PureRenderer, IRow } from '@kanaries/gra"
},
{
"path": "app/src/components/runcellBanner/index.tsx",
"chars": 3153,
"preview": "import React, { useState } from \"react\";\nimport { tracker } from \"@/utils/tracker\";\n\nconst RUNCELL_LOGO_URL = \"https://w"
},
{
"path": "app/src/components/ui/badge.tsx",
"chars": 1140,
"preview": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/"
},
{
"path": "app/src/components/ui/button.tsx",
"chars": 1836,
"preview": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class"
},
{
"path": "app/src/components/ui/checkbox.tsx",
"chars": 1029,
"preview": "import * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { CheckIcon } from \""
},
{
"path": "app/src/components/ui/dialog.tsx",
"chars": 4072,
"preview": "import * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { Cross2Icon } from \"@ra"
},
{
"path": "app/src/components/ui/input.tsx",
"chars": 801,
"preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface InputProps\n extends React.InputHTMLA"
},
{
"path": "app/src/components/ui/label.tsx",
"chars": 710,
"preview": "import * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\nimport { cva, type VariantProps }"
},
{
"path": "app/src/components/ui/select.tsx",
"chars": 5747,
"preview": "import * as React from \"react\"\nimport {\n CaretSortIcon,\n CheckIcon,\n ChevronDownIcon,\n ChevronUpIcon,\n} from \"@radix"
},
{
"path": "app/src/components/ui/tabs.tsx",
"chars": 1877,
"preview": "import * as React from \"react\"\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\"\n\nimport { cn } from \"@/lib/utils\"\n\n"
},
{
"path": "app/src/components/ui/toggle-group.tsx",
"chars": 1734,
"preview": "import * as React from \"react\"\nimport * as ToggleGroupPrimitive from \"@radix-ui/react-toggle-group\"\nimport { VariantProp"
},
{
"path": "app/src/components/ui/toggle.tsx",
"chars": 1391,
"preview": "import * as React from \"react\"\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\"\nimport { cva, type VariantProps"
},
{
"path": "app/src/components/uploadChartModal/index.tsx",
"chars": 10056,
"preview": "import React, { useState, useEffect } from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport type { IGWHandler"
},
{
"path": "app/src/components/uploadSpecModal/index.tsx",
"chars": 8094,
"preview": "import React, { useEffect, useState } from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport type { VizSpecSto"
},
{
"path": "app/src/dataSource/index.tsx",
"chars": 6160,
"preview": "import type { IDataSourceProps } from \"../interfaces\";\nimport type { IRow, IDataQueryPayload } from \"@kanaries/graphic-w"
},
{
"path": "app/src/index.css",
"chars": 1515,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n :root {\n --background: 0 0% 100%;\n "
},
{
"path": "app/src/index.tsx",
"chars": 24978,
"preview": "import React, { useCallback, useContext, useEffect, useState } from 'react';\nimport { createRoot } from 'react-dom/clien"
},
{
"path": "app/src/interfaces/index.ts",
"chars": 1238,
"preview": "import type { IRow, IMutField } from '@kanaries/graphic-walker/interfaces'\nimport type { IDarkMode, IThemeKey, IComputat"
},
{
"path": "app/src/lib/dslToWorkflow.ts",
"chars": 188,
"preview": "import { chartToWorkflow } from \"@kanaries/graphic-walker/utils/workflow\";\n\nexport default function Transform(str: strin"
},
{
"path": "app/src/lib/utils.ts",
"chars": 167,
"preview": "import { type ClassValue, clsx } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n \nexport function cn(...inputs: Cl"
},
{
"path": "app/src/lib/vegaToDsl.ts",
"chars": 287,
"preview": "import { VegaliteMapper } from '@kanaries/graphic-walker/lib/vl2gw';\n\nexport default function Transform(str: string) {\n "
},
{
"path": "app/src/notify/index.tsx",
"chars": 4040,
"preview": "import { Fragment } from \"react\";\nimport { Transition } from \"@headlessui/react\";\nimport { CheckCircleIcon } from \"@hero"
},
{
"path": "app/src/store/common.ts",
"chars": 2446,
"preview": "import { makeObservable, observable, action } from 'mobx';\nimport { ReactElement } from \"react\";\n\ninterface IInitModalIn"
},
{
"path": "app/src/store/communication.ts",
"chars": 473,
"preview": "import { makeObservable, observable, action } from 'mobx';\nimport { ICommunication } from '../utils/communication';\n\ncla"
},
{
"path": "app/src/store/context.ts",
"chars": 335,
"preview": "import { createContext } from 'react';\n\nimport { composeContext } from \"@/utils/context\";\n\nexport const portalContainerC"
},
{
"path": "app/src/tools/exportDataframe.tsx",
"chars": 2875,
"preview": "import React, { useState } from 'react';\nimport communicationStore from \"../store/communication\"\nimport commonStore from"
},
{
"path": "app/src/tools/exportTool.tsx",
"chars": 672,
"preview": "import React from 'react';\n\nimport { tracker } from \"@/utils/tracker\";\nimport { CodeBracketSquareIcon } from '@heroicons"
},
{
"path": "app/src/tools/openDesktop.tsx",
"chars": 1033,
"preview": "import React from \"react\";\n\nimport { tracker } from \"@/utils/tracker\";\nimport { ComputerDesktopIcon } from \"@heroicons/r"
},
{
"path": "app/src/tools/runcellTool.tsx",
"chars": 909,
"preview": "import React from \"react\";\n\nimport { tracker } from \"@/utils/tracker\";\nimport type { ToolbarButtonItem } from \"@kanaries"
},
{
"path": "app/src/tools/saveTool.tsx",
"chars": 5342,
"preview": "import React, { useEffect, useState, useMemo } from 'react';\nimport communicationStore from \"../store/communication\"\nimp"
},
{
"path": "app/src/utils/communication.tsx",
"chars": 10818,
"preview": "import { v4 as uuidv4 } from 'uuid';\nimport commonStore from '../store/common';\nimport { Streamlit } from \"streamlit-com"
},
{
"path": "app/src/utils/context.tsx",
"chars": 593,
"preview": "import React, { Context, ContextType } from 'react';\n\nexport function composeContext<T extends Record<string, Context<an"
},
{
"path": "app/src/utils/formatSpec.ts",
"chars": 472,
"preview": "import { VegaliteMapper } from '@kanaries/graphic-walker/lib/vl2gw';\n\nexport default function FormatSpec(spec: any[], fi"
},
{
"path": "app/src/utils/save.ts",
"chars": 1951,
"preview": "import * as htmlToImage from 'html-to-image';\nimport type { IChartExportResult } from '@kanaries/graphic-walker/interfac"
},
{
"path": "app/src/utils/theme.ts",
"chars": 334,
"preview": "export function currentMediaTheme(dark: \"dark\" | \"light\" | \"media\"): \"dark\" | \"light\" {\n if (dark === \"media\") {\n "
},
{
"path": "app/src/utils/tracker.ts",
"chars": 679,
"preview": "import { AnalyticsBrowser } from '@segment/analytics-next'\n\n\nconst initTracker = () => {\n var userId = \"\";\n var op"
},
{
"path": "app/src/utils/userConfig.ts",
"chars": 313,
"preview": "import { IUserConfig } from \"../interfaces\";\n\nlet config: IUserConfig = {\n privacy: 'events',\n};\n\nexport function che"
},
{
"path": "app/tailwind.config.js",
"chars": 2125,
"preview": "/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n darkMode: [\"class\"],\n content: [\n './pages/**/*.{ts"
},
{
"path": "app/tsconfig.json",
"chars": 803,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ESNext\",\n \"lib\": [\"DOM\", \"DOM.Iterable\", \"ESNext\"],\n \"allowJs\": tr"
},
{
"path": "app/vite.config.ts",
"chars": 2529,
"preview": "import { defineConfig, ConfigEnv, UserConfig } from 'vite'\nimport wasm from 'vite-plugin-wasm';\nimport path from 'path';"
},
{
"path": "bin/pygwalker_command.py",
"chars": 3790,
"preview": "\"\"\"\nPyGWalker is a python library that simplify your Jupyter Notebook data analysis \nand data visualization workflow, by"
},
{
"path": "docs/CONTRIBUTING.md",
"chars": 67,
"preview": "## How to Develop PyGWalker Locally\n\n```bash\nnpm run dev:server\n```"
},
{
"path": "docs/DEVELOPMENT.md",
"chars": 3206,
"preview": "# PyGWalker Development Setup\n\nThis guide explains how to set up a local development environment for PyGWalker with hot-"
},
{
"path": "docs/README.de.md",
"chars": 12895,
"preview": "> Wenn Sie ein Muttersprachler der aktuellen Sprache sind, laden wir Sie ein, uns bei der Pflege der Übersetzung dieses "
},
{
"path": "docs/README.es.md",
"chars": 12697,
"preview": "> Si eres un hablante nativo del idioma actual, te invitamos a ayudarnos a mantener la traducción de este documento. Pue"
},
{
"path": "docs/README.fr.md",
"chars": 12913,
"preview": "> Si vous êtes un locuteur natif de la langue actuelle, nous vous invitons à nous aider à maintenir la traduction de ce "
},
{
"path": "docs/README.ja.md",
"chars": 10592,
"preview": "> 現在の言語のネイティブスピーカーであれば、このドキュメントの翻訳を維持するためにご協力いただけると幸いです。PRは[こちら](https://github.com/Kanaries/pygwalker/pulls)から行うことができます"
},
{
"path": "docs/README.ko.md",
"chars": 10686,
"preview": "> PyGWalker 0.3 is released! Check out the [changelog](https://github.com/Kanaries/pygwalker/releases/tag/0.3.0) for mor"
},
{
"path": "docs/README.ru.md",
"chars": 12967,
"preview": "[English](README.md) | [Español](./docs/README.es.md) | [Français](./docs/README.fr.md) | [Deutsch](./docs/README.de.md)"
},
{
"path": "docs/README.tr.md",
"chars": 13622,
"preview": "> Eğer mevcut dilin anadiliyseniz, bu belgenin çevirisini güncel tutmamıza yardımcı olmaya hoş geldiniz. Bir PR [buradan"
},
{
"path": "docs/README.zh.md",
"chars": 13263,
"preview": "> 如果您是当前语言的母语使用者,欢迎帮助我们维护本文档的翻译。您可以在[这里](https://github.com/Kanaries/pygwalker/pulls)提交PR。\n\n<p align=\"center\"><a href=\"h"
},
{
"path": "environment.yml",
"chars": 167,
"preview": "name: pygwalker\nchannels:\n - conda-forge\n - defaults\ndependencies:\n - ipython\n - jinja2\n - pandas\n - python>=3.5\n "
},
{
"path": "examples/README.md",
"chars": 1332,
"preview": "# Examples\nThis folder contains example implementations of Pygwalker across different interfaces.\n\n- [`component_demo.ip"
},
{
"path": "examples/component_demo.ipynb",
"chars": 3932,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": null,\n \"id\": \"2142be9e-460c-45af-88aa-d356954b0caa\",\n "
},
{
"path": "examples/dash_demo.py",
"chars": 450,
"preview": "import dash\nimport dash_dangerously_set_inner_html\nimport pandas as pd\nimport pygwalker as pyg\n\ndf = pd.read_csv(\"https:"
},
{
"path": "examples/gradio_demo.py",
"chars": 422,
"preview": "import gradio as gr\nimport pandas as pd\n\nfrom pygwalker.api.gradio import PYGWALKER_ROUTE, get_html_on_gradio\n\nwith gr.B"
},
{
"path": "examples/gw_config.json",
"chars": 11183,
"preview": "{\"config\": [{\"config\": {\"defaultAggregated\": true, \"geoms\": [\"bar\"], \"coordSystem\": \"generic\", \"limit\": -1}, \"encodings\""
},
{
"path": "examples/html_demo.py",
"chars": 257,
"preview": "import pygwalker as pyg\nimport pandas as pd\n\n\ndf = pd.read_csv(\"https://kanaries-app.s3.ap-northeast-1.amazonaws.com/pub"
},
{
"path": "examples/jupyter_demo.ipynb",
"chars": 1224,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": null,\n \"id\": \"0934673e-22de-45e6-9f70-438e8c4c49d3\",\n "
},
{
"path": "examples/marimo_demo.py",
"chars": 899,
"preview": "# /// script\n# requires-python = \">=3.12\"\n# dependencies = [\n# \"marimo\",\n# \"pandas==2.2.3\",\n# \"pygwalker==0."
},
{
"path": "examples/reflex_demo/.gitignore",
"chars": 375,
"preview": ".web\n.states\nassets/external/\n*.db\n*.py[cod]\n# Reflex generated files and directories\n.web/\n__pycache__/\nrequirements.tx"
},
{
"path": "examples/reflex_demo/README.md",
"chars": 2490,
"preview": "# PyGWalker Reflex Demo\n\nThis demo shows how to integrate PyGWalker with Reflex, a Python web framework for building int"
},
{
"path": "examples/reflex_demo/__init__.py",
"chars": 66,
"preview": "\"\"\"Pygwalker examples package.\"\"\" \n\n# Initialize examples package "
},
{
"path": "examples/reflex_demo/app/__init__.py",
"chars": 41,
"preview": "\"\"\"PyGWalker Reflex Demo App Package.\"\"\" "
},
{
"path": "examples/reflex_demo/app/app.py",
"chars": 5868,
"preview": "\"\"\"\nPyGWalker + Reflex integration demo.\n\nThis demo shows how to integrate PyGWalker with Reflex, a Python web framework"
},
{
"path": "examples/reflex_demo/rxconfig.py",
"chars": 108,
"preview": "import reflex as rx\n\nconfig = rx.Config(\n app_name=\"app\",\n backend_port=8000,\n loglevel=\"debug\",\n) "
},
{
"path": "examples/streamlit_demo.py",
"chars": 1193,
"preview": "from pygwalker.api.streamlit import StreamlitRenderer\nimport pandas as pd\nimport streamlit as st\n\n# Adjust the width of "
},
{
"path": "examples/web_server_demo.py",
"chars": 2437,
"preview": "\"\"\"\npygwalker>=0.4.8.6\n\nThis is poc for PygWalker integration with FastAPI.\n\nIf you want to use Graphic-Walker in your w"
},
{
"path": "pygwalker/__init__.py",
"chars": 1049,
"preview": "from pygwalker.utils.log import init_logging as __init_logging\n\n__init_logging()\n\n# pylint: disable=wrong-import-positio"
},
{
"path": "pygwalker/_constants.py",
"chars": 132,
"preview": "import os\n\nJUPYTER_BYTE_LIMIT = 1 << 24\nJUPYTER_WIDGETS_BYTE_LIMIT = 1 << 20\n\nROOT_DIR = os.path.dirname(os.path.abspath"
},
{
"path": "pygwalker/_typing.py",
"chars": 885,
"preview": "from typing import TypeVar, TYPE_CHECKING\n\nfrom typing_extensions import Literal\n\ndataframe_types = []\nif TYPE_CHECKING:"
},
{
"path": "pygwalker/api/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "pygwalker/api/adapter.py",
"chars": 6212,
"preview": "\nfrom typing import Union, List, Optional\n\nfrom typing_extensions import Literal\n\nfrom pygwalker.data_parsers.base impor"
},
{
"path": "pygwalker/api/anywidget.py",
"chars": 2895,
"preview": "from typing import Union, List, Optional\nimport inspect\nimport json\nimport pathlib\n\nfrom typing_extensions import Litera"
},
{
"path": "pygwalker/api/component.py",
"chars": 17960,
"preview": "from typing import List, Optional, Dict, Any, Union\nfrom typing_extensions import Literal\nfrom copy import deepcopy\n\nfro"
},
{
"path": "pygwalker/api/gradio.py",
"chars": 2957,
"preview": "from typing import Union, List, Optional\nfrom typing_extensions import Literal\n\nfrom .pygwalker import PygWalker\nfrom py"
},
{
"path": "pygwalker/api/html.py",
"chars": 6305,
"preview": "from typing import Union, Dict, Optional, Any, List\nimport logging\n\nfrom typing_extensions import Literal\n\nfrom .pygwalk"
},
{
"path": "pygwalker/api/jupyter.py",
"chars": 6609,
"preview": "from typing import Union, List, Optional\nimport inspect\n\nfrom typing_extensions import Literal\n\nfrom .pygwalker import P"
},
{
"path": "pygwalker/api/kanaries_cloud.py",
"chars": 2996,
"preview": "from typing import List, Optional, Union\nfrom datetime import datetime\n\nfrom pygwalker.data_parsers.base import FieldSpe"
},
{
"path": "pygwalker/api/marimo.py",
"chars": 2929,
"preview": "from typing import Union, List, Optional\nimport inspect\nimport json\nimport pathlib\n\nfrom typing_extensions import Litera"
},
{
"path": "pygwalker/api/pygwalker.py",
"chars": 25779,
"preview": "import base64\nfrom typing import List, Dict, Any, Optional, Union\nimport urllib\nimport json\nimport os, sys, subprocess\ni"
},
{
"path": "pygwalker/api/reflex.py",
"chars": 1923,
"preview": "from typing import Union, List, Optional\n\nimport reflex as rx\nfrom typing_extensions import Literal\n\nfrom .pygwalker imp"
},
{
"path": "pygwalker/api/streamlit.py",
"chars": 12517,
"preview": "from typing import Union, Dict, Optional, List, Any, Tuple\nfrom packaging.version import Version\nfrom copy import deepco"
},
{
"path": "pygwalker/api/webserver.py",
"chars": 11093,
"preview": "from typing import Union, List, Optional\nimport threading\nimport socketserver\nimport http.server\nimport urllib.parse\nimp"
},
{
"path": "pygwalker/communications/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "pygwalker/communications/anywidget_comm.py",
"chars": 1295,
"preview": "from typing import Any, Dict, Optional, List\nimport uuid\nimport json\n\nimport anywidget\n\nfrom .base import BaseCommunicat"
},
{
"path": "pygwalker/communications/base.py",
"chars": 1651,
"preview": "from typing import Any, Callable, Dict\n\nfrom pygwalker.errors import BaseError, ErrorCode\nfrom pygwalker.services.track "
},
{
"path": "pygwalker/communications/gradio_comm.py",
"chars": 1580,
"preview": "import json\nimport gc\n\nfrom fastapi import FastAPI\nfrom starlette.routing import Route\nfrom starlette.responses import J"
},
{
"path": "pygwalker/communications/hacker_comm.py",
"chars": 2540,
"preview": "from threading import Lock\nfrom typing import Any, Dict, Optional, List\nimport uuid\nimport json\nimport time\n\nfrom ipywid"
},
{
"path": "pygwalker/communications/reflex_comm.py",
"chars": 3850,
"preview": "import json\nfrom fastapi import FastAPI, HTTPException\nfrom starlette.routing import Route\nfrom starlette.responses impo"
},
{
"path": "pygwalker/communications/streamlit_comm.py",
"chars": 1999,
"preview": "import gc\nimport json\n\nfrom tornado.web import Application\nfrom streamlit import config\nfrom streamlit.web.server.server"
},
{
"path": "pygwalker/data_parsers/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "pygwalker/data_parsers/base.py",
"chars": 8631,
"preview": "from typing import Generic, Dict, List, Any, Optional\nfrom typing_extensions import Literal\nfrom functools import lru_ca"
},
{
"path": "pygwalker/data_parsers/cloud_dataset_parser.py",
"chars": 3956,
"preview": "from typing import Any, Dict, List, Optional\nfrom functools import lru_cache\nfrom decimal import Decimal\nimport logging\n"
},
{
"path": "pygwalker/data_parsers/database_parser.py",
"chars": 9796,
"preview": "from typing import Any, Dict, List, Optional\nfrom functools import lru_cache\nfrom decimal import Decimal\nimport logging\n"
},
{
"path": "pygwalker/data_parsers/modin_parser.py",
"chars": 2297,
"preview": "import io\nfrom typing import Any, Dict, List, Optional\n\nfrom modin import pandas as mpd\n\nfrom .base import (\n BaseDat"
},
{
"path": "pygwalker/data_parsers/pandas_parser.py",
"chars": 1885,
"preview": "from typing import Any, Dict, List, Optional\nimport io\n\nimport pandas as pd\n\nfrom .base import (\n BaseDataFrameDataPa"
},
{
"path": "pygwalker/data_parsers/polars_parser.py",
"chars": 1903,
"preview": "from typing import List, Any, Dict, Optional\nimport io\n\nimport polars as pl\n\nfrom .base import (\n BaseDataFrameDataPa"
},
{
"path": "pygwalker/data_parsers/spark_parser.py",
"chars": 4116,
"preview": "from typing import Any, Dict, List, Optional\nfrom functools import lru_cache\nimport logging\nimport io\n\nfrom pyspark.sql "
},
{
"path": "pygwalker/errors.py",
"chars": 1288,
"preview": "\"\"\"\n This module contains all the custom errors used by pygwalker.\n\"\"\"\nfrom enum import Enum\n\n\nclass ErrorCode(int, E"
},
{
"path": "pygwalker/services/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "pygwalker/services/check_update.py",
"chars": 2395,
"preview": "\"\"\"\nCheck_Update Module: This module enables you to check if\nthe version is up-to-date.\n\nUpdated on Tue November 11 15:1"
},
{
"path": "pygwalker/services/cloud_service.py",
"chars": 18547,
"preview": "from typing import List, Dict, Any, Optional\nfrom datetime import datetime\nfrom urllib.parse import urlencode\nfrom typin"
},
{
"path": "pygwalker/services/config.py",
"chars": 4375,
"preview": "import os\nimport json\nfrom typing import List, Optional, Dict\nfrom functools import lru_cache\n\nfrom pygwalker.utils.rand"
},
{
"path": "pygwalker/services/data_parsers.py",
"chars": 5824,
"preview": "import sys\nimport hashlib\nimport pandas as pd\nfrom typing import Dict, Optional, Union, Any, List, Tuple\nfrom typing_ext"
},
{
"path": "pygwalker/services/fname_encodings.py",
"chars": 1515,
"preview": "from typing import List\nfrom math import ceil\nfrom collections import defaultdict\n\n\ndef base36encode(s: str) -> str:\n "
},
{
"path": "pygwalker/services/format_invoke_walk_code.py",
"chars": 2231,
"preview": "from typing import Optional, List\nfrom types import FrameType\nimport logging\nimport inspect\nimport ast\n\nfrom astor.sourc"
},
{
"path": "pygwalker/services/global_var.py",
"chars": 1771,
"preview": "import os\n\nfrom pandas import DataFrame\nfrom typing_extensions import Literal, deprecated\n\nfrom .config import get_confi"
},
{
"path": "pygwalker/services/kaggle.py",
"chars": 1649,
"preview": "from functools import lru_cache\n\nfrom pygwalker.services.global_var import GlobalVarManager\nfrom pygwalker.utils.display"
},
{
"path": "pygwalker/services/kanaries_cli_login.py",
"chars": 2666,
"preview": "from http.server import BaseHTTPRequestHandler, HTTPServer\nfrom typing import Any\nfrom urllib.parse import urlparse, par"
},
{
"path": "pygwalker/services/preview_image.py",
"chars": 3732,
"preview": "from typing import List, Dict, Any\nfrom concurrent.futures.thread import ThreadPoolExecutor\nimport base64\nimport zlib\nim"
},
{
"path": "pygwalker/services/render.py",
"chars": 2490,
"preview": "import os\nimport json\nimport base64\nimport html as m_html\nfrom typing import Dict, List, Any, Optional\nimport zlib\n\nfrom"
},
{
"path": "pygwalker/services/spec.py",
"chars": 6871,
"preview": "from urllib import request\nfrom typing import Tuple, Dict, Any, List, Union\nfrom packaging.version import Version\nfrom c"
},
{
"path": "pygwalker/services/streamlit_components.py",
"chars": 475,
"preview": "from typing import Dict, Any\nimport os\n\nfrom streamlit.components.v1.components import CustomComponent\nimport streamlit."
},
{
"path": "pygwalker/services/tip_tools.py",
"chars": 802,
"preview": "from threading import Thread\nimport time\n\nfrom pygwalker.utils.display import display_html\n\nWIDGETS_TIPS = \"\"\"\n<div styl"
},
{
"path": "pygwalker/services/track.py",
"chars": 2142,
"preview": "from typing import Dict, Any, Optional\n\nimport segment.analytics as analytics\nimport kanaries_track\n\nfrom pygwalker.serv"
},
{
"path": "pygwalker/services/upload_data.py",
"chars": 3008,
"preview": "from typing import Dict, Any, List\nimport time\nimport json\nimport html as m_html\n\nfrom pygwalker.utils.randoms import ra"
},
{
"path": "pygwalker/templates/.gitignore",
"chars": 7,
"preview": "/dist/*"
},
{
"path": "pygwalker/templates/index.html",
"chars": 467,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<!-- temporary streamlit entry -->\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equ"
},
{
"path": "pygwalker/templates/jupyter_iframe_message.html",
"chars": 636,
"preview": "<script>\n window.addEventListener(\"message\", function(event) {\n const backgroundMap = {\n \"dark\": \"h"
},
{
"path": "pygwalker/templates/pygwalker_iframe.html",
"chars": 1592,
"preview": "{% if component_url == \"\" %}\n<div id=\"ifr-pyg-{{ gid }}\" style=\"height: auto\">\n <head>\n <meta http-equiv=\"Cont"
},
{
"path": "pygwalker/templates/pygwalker_main_page.html",
"chars": 2098,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\" />\n</head>"
},
{
"path": "pygwalker/utils/__init__.py",
"chars": 173,
"preview": "\ndef fallback_value(*values):\n \"\"\"Return the first non-None value in a list of values.\"\"\"\n for value in values:\n "
},
{
"path": "pygwalker/utils/check_walker_params.py",
"chars": 494,
"preview": "import logging\nfrom typing import Dict, Any\n\nlogger = logging.getLogger(__name__)\n\n\ndef check_expired_params(params: Dic"
},
{
"path": "pygwalker/utils/custom_sqlglot.py",
"chars": 10632,
"preview": "from sqlglot.dialects.duckdb import DuckDB as DuckdbDialect\nfrom sqlglot.dialects.postgres import Postgres as PostgresDi"
},
{
"path": "pygwalker/utils/display.py",
"chars": 882,
"preview": "from typing import Union\n\nfrom IPython.display import display, HTML\nimport ipywidgets\n\nDISPLAY_HANDLER = {}\n\n\ndef displa"
},
{
"path": "pygwalker/utils/dsl_transform.py",
"chars": 2010,
"preview": "from typing import Dict, List, Any, Optional, Callable\nimport os\nimport json\n\nfrom pygwalker._constants import ROOT_DIR\n"
},
{
"path": "pygwalker/utils/encode.py",
"chars": 661,
"preview": "import json\nfrom datetime import datetime\nfrom decimal import Decimal\n\nimport pytz\n\n\nclass DataFrameEncoder(json.JSONEnc"
},
{
"path": "pygwalker/utils/estimate_tools.py",
"chars": 461,
"preview": "from typing import List, Dict, Any\nimport json\n\nfrom .encode import DataFrameEncoder\n\n\ndef estimate_average_data_size(da"
},
{
"path": "pygwalker/utils/execute_env_check.py",
"chars": 725,
"preview": "import psutil\nimport re\nimport os\n\nfrom typing_extensions import Literal\n\n\ndef check_convert() -> bool:\n \"\"\"\n Chec"
},
{
"path": "pygwalker/utils/free_port.py",
"chars": 272,
"preview": "import socket\n\n\ndef find_free_port() -> int:\n \"\"\"Find a free port on localhost\"\"\"\n temp_socket = socket.socket(soc"
},
{
"path": "pygwalker/utils/log.py",
"chars": 284,
"preview": "import logging\n\n\ndef init_logging():\n logger = logging.getLogger(\"pygwalker\")\n logger.setLevel(logging.INFO)\n h"
},
{
"path": "pygwalker/utils/payload_to_sql.py",
"chars": 539,
"preview": "from typing import Dict, List, Any\n\n\ndef get_sql_from_payload(\n table_name: str,\n payload: Dict[str, Any],\n fie"
},
{
"path": "pygwalker/utils/randoms.py",
"chars": 412,
"preview": "from datetime import datetime, timezone\nimport random\nimport string\n\n\ndef rand_str(n: int = 8, options: str = string.asc"
},
{
"path": "pygwalker/utils/runtime_env.py",
"chars": 533,
"preview": "from typing_extensions import Literal\n\n\ndef _is_jupyter() -> bool:\n try:\n from IPython import get_ipython\n "
},
{
"path": "pygwalker_tools/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "pygwalker_tools/metrics/__init__.py",
"chars": 98,
"preview": "from .api import get_metrics_datas, MetricsChart\n\n__all__ = [\"get_metrics_datas\", \"MetricsChart\"]\n"
},
{
"path": "pygwalker_tools/metrics/api.py",
"chars": 8456,
"preview": "\"\"\"\nExperimental features\n\"\"\"\nfrom typing import List, Dict, Any, Union, Optional\nfrom decimal import Decimal\nimport jso"
},
{
"path": "pygwalker_tools/metrics/core.py",
"chars": 8526,
"preview": "from typing import Dict, Any, Tuple, List\n\nimport sqlglot.expressions as exp\nimport sqlglot\n\nMETRICS_DEFINITIONS = {\n "
},
{
"path": "pyproject.toml",
"chars": 3092,
"preview": "[project]\nname = \"pygwalker\"\ndynamic = [\"version\"]\nrequires-python = \">=3.7\"\ndescription = \"pygwalker: turn your data in"
},
{
"path": "scripts/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "scripts/ci_run_pytest.py",
"chars": 2608,
"preview": "#!/usr/bin/env python3\nimport subprocess\nimport sys\nimport time\nimport xml.etree.ElementTree as ET\nfrom pathlib import P"
},
{
"path": "scripts/compile.sh",
"chars": 241,
"preview": "#!/bin/sh\ncur_dir=$(pwd)\nfile_dir=$(dirname $(dirname $0) )\necho $cur_dir\necho $file_dir\nAPP=$file_dir/app\n\n(cd $file_di"
},
{
"path": "scripts/test-init.py",
"chars": 525,
"preview": "import os\nimport sys\nfrom urllib.request import urlretrieve\n\n# URL of the CSV file to download\nurl = \"https://kanaries-a"
},
{
"path": "scripts/test-init.sh",
"chars": 1431,
"preview": "#!/usr/bin/env bash\n\nset -e # Exit immediately if a command exits with a non-zero status.\n\n# URL of the CSV file to dow"
},
{
"path": "tests/.gitignore",
"chars": 18,
"preview": ".ipynb_checkpoints"
},
{
"path": "tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/field-spec.ipynb",
"chars": 8483,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": 1,\n \"id\": \"fc76f90c-8b29-406d-b322-476833d1d0b6\",\n \""
},
{
"path": "tests/main-modin.ipynb",
"chars": 723,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": null,\n \"metadata\": {},\n \"outputs\": [],\n \"source\": "
},
{
"path": "tests/main-polars.ipynb",
"chars": 977,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": null,\n \"id\": \"9ef916d3-8e0d-4701-84f2-85cc245ca887\",\n "
},
{
"path": "tests/main.ipynb",
"chars": 14235,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": null,\n \"id\": \"73dcdeb8-816c-4185-8630-a51b3df25765\",\n "
},
{
"path": "tests/test_component_api.ipynb",
"chars": 3932,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": null,\n \"id\": \"2142be9e-460c-45af-88aa-d356954b0caa\",\n "
},
{
"path": "tests/test_data_parsers.py",
"chars": 4538,
"preview": "import os.path\n\nfrom sqlalchemy import create_engine\nimport pandas as pd\nimport polars as pl\nimport pytest\n\nfrom pygwalk"
},
{
"path": "tests/test_dsl_transform.py",
"chars": 2476,
"preview": "from unittest import mock\nimport builtins\n\nimport pytest\n\nimport pygwalker.utils.dsl_transform as mod\nfrom pygwalker.uti"
},
{
"path": "tests/test_fname_encodings.py",
"chars": 844,
"preview": "from pygwalker.services.fname_encodings import (\n base36encode,\n fname_decode,\n fname_encode,\n base36decode\n"
},
{
"path": "tests/test_format_invoke_walk_code.py",
"chars": 704,
"preview": "from pygwalker.services.format_invoke_walk_code import get_formated_spec_params_code\n\n\ndef test_get_formated_spec_params"
},
{
"path": "tutorials/README.md",
"chars": 793,
"preview": "# PyGWalker Tutorials\n\nThis directory contains in-depth, step-by-step tutorials designed to complement the concise scrip"
},
{
"path": "tutorials/pygwalker_complete_tutorial.ipynb",
"chars": 127001,
"preview": "{\n \"nbformat\": 4,\n \"nbformat_minor\": 0,\n \"metadata\": {\n \"colab\": {\n \"provenance\": [],\n \"collapsed_sectio"
}
]
About this extraction
This page contains the full source code of the Kanaries/pygwalker GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 180 files (742.4 KB), approximately 199.4k tokens, and a symbol index with 571 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.