diff --git a/.gitignore b/.gitignore
index 2620d7f..5f20c23 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ screenshots
# Python
__pycache__
*.pyc
+venv
# IDE
.idea
diff --git a/pandasgui/constants.py b/pandasgui/constants.py
index 8ba6f55..6f2e3bc 100644
--- a/pandasgui/constants.py
+++ b/pandasgui/constants.py
@@ -1,8 +1,18 @@
import os
+import sys
from appdirs import user_data_dir
+import pkg_resources
LOCAL_DATA_DIR = os.path.join(user_data_dir(), "pandasgui")
LOCAL_DATASET_DIR = os.path.join(LOCAL_DATA_DIR, "dataset_files")
os.makedirs(LOCAL_DATA_DIR, exist_ok=True)
os.makedirs(LOCAL_DATASET_DIR, exist_ok=True)
+
+PANDASGUI_ICON_PATH = pkg_resources.resource_filename(__name__, "resources/images/icon.png")
+PANDASGUI_ICON_PATH_ICO = pkg_resources.resource_filename(__name__, "resources/images/icon.ico")
+
+if sys.platform == "win32":
+ SHORTCUT_PATH = os.path.join(os.getenv('APPDATA'), 'Microsoft/Windows/Start Menu/Programs/PandasGUI.lnk', )
+ PY_INTERPRETTER_PATH = os.path.join(os.path.dirname(sys.executable), 'python.exe')
+ PYW_INTERPRETTER_PATH = os.path.join(os.path.dirname(sys.executable), 'pythonw.exe')
\ No newline at end of file
diff --git a/pandasgui/datasets.py b/pandasgui/datasets.py
index b363ef3..2a12a84 100644
--- a/pandasgui/datasets.py
+++ b/pandasgui/datasets.py
@@ -171,7 +171,7 @@ def __getattr__(name: str) -> Union[pd.DataFrame, Dict[str, pd.DataFrame]]:
################
# These just improve intellisense since the type hint on the __getattr__ return value didn't work
-all_datasets: pd.DataFrame
+all_datasets: Dict[str, pd.DataFrame]
pokemon: pd.DataFrame
googleplaystore: pd.DataFrame
googleplaystore_reviews: pd.DataFrame
diff --git a/pandasgui/gui.py b/pandasgui/gui.py
index 3b85b2b..d1c233f 100644
--- a/pandasgui/gui.py
+++ b/pandasgui/gui.py
@@ -1,16 +1,17 @@
import inspect
+import io
import os
import sys
import pprint
from typing import Callable, Union
from dataclasses import dataclass
import pandas as pd
-import pkg_resources
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt
import pandasgui
-from pandasgui.store import PandasGuiStore
+from pandasgui.constants import PANDASGUI_ICON_PATH
+from pandasgui.store import PandasGuiStore, SettingsSchema
from pandasgui.utility import as_dict, fix_ipython, get_figure_type, resize_widget
from pandasgui.widgets.find_toolbar import FindToolbar
from pandasgui.widgets.json_viewer import JsonViewer
@@ -42,7 +43,7 @@ def except_hook(cls, exception, traceback):
class PandasGui(QtWidgets.QMainWindow):
- def __init__(self, settings: dict = {}, **kwargs):
+ def __init__(self, settings: SettingsSchema = {}, **kwargs):
"""
Args:
settings: Dict of settings, as defined in pandasgui.store.SettingsStore
@@ -70,41 +71,44 @@ def __init__(self, settings: dict = {}, **kwargs):
# Create all widgets
self.init_ui()
- plotly_kwargs = {key: value for (key, value) in kwargs.items() if get_figure_type(value) is not None}
- json_kwargs = {key: value for (key, value) in kwargs.items() if any([
- issubclass(type(value), list),
- issubclass(type(value), dict),
- ])}
- dataframe_kwargs = {key: value for (key, value) in kwargs.items() if any([
- issubclass(type(value), pd.DataFrame),
- issubclass(type(value), pd.Series),
- ])}
-
- if json_kwargs:
- for name, val in json_kwargs.items():
- jv = JsonViewer(val)
- jv.setWindowTitle(name)
- self.store.add_item(jv, name)
-
- if plotly_kwargs:
- for name, fig in plotly_kwargs.items():
- pv = FigureViewer(fig)
- pv.setWindowTitle(name)
- self.store.add_item(pv, name)
-
- if dataframe_kwargs:
- # Adds DataFrames listed in kwargs to data store.
- for df_name, df in dataframe_kwargs.items():
- self.store.add_dataframe(df, df_name)
-
+ # Add the item to the store differently depending what type it is
+ for key, value in kwargs.items():
+ # Pandas objects
+ if issubclass(type(value), pd.DataFrame) or issubclass(type(value), pd.Series):
+ self.store.add_dataframe(value, key)
+ # Spark
+ elif hasattr(value, 'toPandas'):
+ temp = value.toPandas()
+ self.store.add_dataframe(value, key)
+ elif hasattr(value, 'to_pandas'):
+ temp = value.to_pandas()
+ self.store.add_dataframe(value, key)
+ # JSON
+ elif issubclass(type(value), list) or issubclass(type(value), dict):
+ jv = JsonViewer(value)
+ jv.setWindowTitle(key)
+ self.store.add_item(jv, key)
+ # Graphs
+ elif get_figure_type(value) is not None:
+ pv = FigureViewer(value)
+ pv.setWindowTitle(key)
+ self.store.add_item(pv, key)
+ # File buffers
+ elif issubclass(type(value), io.BytesIO) or issubclass(type(value), io.StringIO):
+ value.seek(0)
+ df = pd.read_csv(value)
+ self.store.add_dataframe(df, key)
+ # File paths
+ elif issubclass(type(value), str):
+ if os.path.exists(value):
+ self.store.import_file(value)
+ else:
+ logger.warning(f"File path is invalid or does not exist: {value}")
+ else:
+ logger.warning(f"PandasGUI Unsupported type: {type(value)}")
# Default to first item
self.navigator.setCurrentItem(self.navigator.topLevelItem(0))
- self.show()
- # Start event loop if blocking enabled
- if self.store.settings.block.value:
- self.app.exec_()
-
# Create and add all widgets to GUI.
def init_ui(self):
self.code_history_viewer = None
@@ -122,8 +126,7 @@ def init_ui(self):
# Set window title and icon
self.setWindowTitle("PandasGUI")
- pdgui_icon_path = pkg_resources.resource_filename(__name__, "resources/images/icon.png")
- self.app.setWindowIcon(QtGui.QIcon(pdgui_icon_path))
+ self.app.setWindowIcon(QtGui.QIcon(PANDASGUI_ICON_PATH))
# Hide the question mark on dialogs
self.app.setAttribute(Qt.AA_DisableWindowContextHelpButton)
@@ -177,27 +180,29 @@ class MenuItem:
func: Callable
shortcut: str = ''
- items = {'Edit': [MenuItem(name='Find',
- func=self.find_bar.show_find_bar,
- shortcut='Ctrl+F'),
- MenuItem(name='Copy',
- func=self.copy,
- shortcut='Ctrl+C'),
- MenuItem(name='Copy With Headers',
- func=self.copy_with_headers,
- shortcut='Ctrl+Shift+C'),
- MenuItem(name='Paste',
- func=self.paste,
- shortcut='Ctrl+V'),
- MenuItem(name='Import',
- func=self.import_dialog),
- MenuItem(name='Import From Clipboard',
- func=self.import_from_clipboard),
- MenuItem(name='Export',
- func=self.export_dialog),
- MenuItem(name='Code Export',
- func=self.show_code_export),
- ],
+ items = {'Edit': [MenuItem(name='Find',
+ func=self.find_bar.show_find_bar,
+ shortcut='Ctrl+F'),
+ MenuItem(name='Copy',
+ func=self.copy,
+ shortcut='Ctrl+C'),
+ MenuItem(name='Copy With Headers',
+ func=self.copy_with_headers,
+ shortcut='Ctrl+Shift+C'),
+ MenuItem(name='Paste',
+ func=self.paste,
+ shortcut='Ctrl+V'),
+ MenuItem(name='Import',
+ func=self.import_dialog),
+ MenuItem(name='Import From Clipboard',
+ func=self.import_from_clipboard),
+ MenuItem(name='Export',
+ func=self.export_dialog),
+ MenuItem(name='Export To Clipboard',
+ func=self.export_to_clipboard),
+ MenuItem(name='Code Export',
+ func=self.show_code_export),
+ ],
'DataFrame': [MenuItem(name='Delete Selected DataFrames',
func=self.delete_selected_dataframes),
MenuItem(name='Reload DataFrames',
@@ -206,27 +211,33 @@ class MenuItem:
MenuItem(name='Parse All Dates',
func=lambda: self.store.selected_pgdf.parse_all_dates()),
],
- 'Settings': [MenuItem(name='Preferences...',
- func=self.edit_settings),
- {"Context Menus": [MenuItem(name='Add PandasGUI To Context Menu',
- func=self.add_to_context_menu),
- MenuItem(name='Remove PandasGUI From Context Menu',
- func=self.remove_from_context_menu),
- MenuItem(name='Add JupyterLab To Context Menu',
- func=self.add_jupyter_to_context_menu),
- MenuItem(name='Remove JupyterLab From Context Menu',
- func=self.remove_jupyter_from_context_menu), ]}
-
- ],
- 'Debug': [MenuItem(name='About',
- func=self.about),
- MenuItem(name='Browse Sample Datasets',
- func=self.show_sample_datasets),
- MenuItem(name='View PandasGuiStore',
- func=self.view_store),
- MenuItem(name='View DataFrame History',
- func=self.view_history),
- ]
+ 'Settings': [MenuItem(name='Preferences...',
+ func=self.edit_settings),
+ {"Context Menus": [MenuItem(name='Add PandasGUI To Context Menu',
+ func=self.add_to_context_menu),
+ MenuItem(name='Remove PandasGUI From Context Menu',
+ func=self.remove_from_context_menu),
+ MenuItem(name='Add PandasGUI To Start Menu',
+ func=self.add_to_start_menu),
+ MenuItem(name='Remove PandasGUI From Start Menu',
+ func=self.remove_from_start_menu),
+ MenuItem(name='Add JupyterLab To Context Menu',
+ func=self.add_jupyter_to_context_menu),
+ MenuItem(name='Remove JupyterLab From Context Menu',
+ func=self.remove_jupyter_from_context_menu), ]}
+
+ ],
+ 'Debug': [MenuItem(name='About',
+ func=self.about),
+ MenuItem(name='Browse Sample Datasets',
+ func=self.show_sample_datasets),
+ MenuItem(name='View PandasGuiStore',
+ func=self.view_store),
+ MenuItem(name='View DataFrame History',
+ func=self.view_history),
+ MenuItem(name='Throw Error',
+ func=lambda x: exec('raise(Exception("Exception raised by PandasGUI"))')),
+ ]
}
def add_menus(dic, root):
@@ -278,7 +289,6 @@ def show_code_export(self):
def update_code_export(self):
self.store.selected_pgdf.dataframe_explorer.code_history_viewer.refresh()
-
def delete_selected_dataframes(self):
for name in [item.text(0) for item in self.navigator.selectedItems()]:
self.store.remove_dataframe(name)
@@ -347,6 +357,9 @@ def import_from_clipboard(self):
skip_blank_lines=False)
self.store.add_dataframe(df)
+ def export_to_clipboard(self):
+ self.store.selected_pgdf.df.to_clipboard(excel=True, index=True)
+
# https://stackoverflow.com/a/29769228/3620725
def add_to_context_menu(self):
import winreg
@@ -367,6 +380,28 @@ def remove_from_context_menu(self):
winreg.DeleteKey(key, "Software\Classes\*\shell\Open with PandasGUI\command")
winreg.DeleteKey(key, "Software\Classes\*\shell\Open with PandasGUI")
+ # https://stackoverflow.com/a/46081847
+ def add_to_start_menu(self):
+ if sys.platform == "win32":
+ import os
+ import win32com.client
+ import pythoncom
+ from pandasgui.constants import PANDASGUI_ICON_PATH_ICO, PYW_INTERPRETTER_PATH, SHORTCUT_PATH
+
+ shell = win32com.client.Dispatch("WScript.Shell")
+ shortcut = shell.CreateShortCut(SHORTCUT_PATH)
+ shortcut.Targetpath = PYW_INTERPRETTER_PATH
+ shortcut.Arguments = '-c "import pandasgui; pandasgui.show()"'
+ shortcut.IconLocation = PANDASGUI_ICON_PATH_ICO
+ shortcut.WindowStyle = 7 # 7 - Minimized, 3 - Maximized, 1 - Normal
+ shortcut.save()
+
+ def remove_from_start_menu(self):
+ if sys.platform == "win32":
+ from pandasgui.constants import SHORTCUT_PATH
+ import os
+ os.remove(SHORTCUT_PATH)
+
def add_jupyter_to_context_menu(self):
import winreg
@@ -436,13 +471,13 @@ def reload_data(self):
def show(*args,
- settings={},
+ settings: SettingsSchema = {},
**kwargs):
'''
Objects provided as args and kwargs should be any of the following:
DataFrame Show it using PandasGui
Series Show it using PandasGui
- Figure Show it using FigureViewer. Supports figures from plotly, bokeh, matplotlib, altair
+ Figure Show it using FigureViewer. Supports figures from plotly, bokeh, matplotlib, altair, PIL
dict/list Show it using JsonViewer
'''
logger.info("Opening PandasGUI")
@@ -487,6 +522,13 @@ def pg(line):
else:
raise e
+ pandas_gui.show()
+ pandas_gui.activateWindow()
+ pandas_gui.raise_()
+
+ # Start event loop if blocking enabled
+ if pandas_gui.store.settings.block.value:
+ pandas_gui.app.exec_()
return pandas_gui
diff --git a/pandasgui/jotly.py b/pandasgui/jotly.py
index 64d1f72..cc8d862 100644
--- a/pandasgui/jotly.py
+++ b/pandasgui/jotly.py
@@ -32,7 +32,7 @@ class OtherDataFrame(DataFrame):
# Graphing
def histogram(data_frame: DataFrame,
- x: ColumnName = None,
+ x: ColumnNameList = None,
color: ColumnName = None,
facet_row: ColumnName = None,
facet_col: ColumnName = None,
@@ -46,6 +46,7 @@ def histogram(data_frame: DataFrame,
facet_col=facet_col,
marginal=marginal,
cumulative=cumulative,
+ # nbins=nbins,
**kwargs)
return fig
@@ -85,7 +86,7 @@ def scatter(data_frame: DataFrame,
def line(data_frame: DataFrame,
- y: ColumnName = None,
+ y: ColumnNameList = None,
x: ColumnName = None,
color: ColumnName = None,
facet_row: ColumnName = None,
@@ -120,7 +121,7 @@ def line(data_frame: DataFrame,
def bar(data_frame: DataFrame,
- y: ColumnName = None,
+ y: ColumnNameList = None,
x: ColumnName = None,
color: ColumnName = None,
facet_row: ColumnName = None,
@@ -156,7 +157,7 @@ def bar(data_frame: DataFrame,
def box(data_frame: DataFrame,
- y: ColumnName = None,
+ y: ColumnNameList = None,
x: ColumnName = None,
color: ColumnName = None,
facet_row: ColumnName = None,
@@ -175,7 +176,7 @@ def box(data_frame: DataFrame,
def violin(data_frame: DataFrame,
- y: ColumnName = None,
+ y: ColumnNameList = None,
x: ColumnName = None,
color: ColumnName = None,
facet_row: ColumnName = None,
diff --git a/pandasgui/store.py b/pandasgui/store.py
index 0c5f5b3..46cdd51 100644
--- a/pandasgui/store.py
+++ b/pandasgui/store.py
@@ -11,7 +11,7 @@
from dataclasses import dataclass, field
from typing import Iterable, List, Union
-from typing_extensions import Literal
+from typing_extensions import Literal, TypedDict
import pandas as pd
from pandas import DataFrame
from PyQt5 import QtCore, QtWidgets
@@ -83,19 +83,30 @@ def __setattr__(self, key, value):
super().__setattr__(key, value)
-DEFAULT_SETTINGS = {'editable': True,
- 'block': None,
- 'theme': 'light',
- 'auto_finish': True,
+DEFAULT_SETTINGS = {'editable': True,
+ 'block': None,
+ 'theme': 'light',
+ 'auto_finish': True,
'refresh_statistics': True,
- 'render_mode': 'auto',
- 'aggregation': 'mean',
- 'title_format': "{name}: {title_columns}{title_dimensions}{names}{title_y}{title_z}{over_by}"
- "{title_x} {selection}
{groupings}{filters} {title_trendline}"
+ 'render_mode': 'auto',
+ 'aggregation': 'mean',
+ 'title_format': "{name}: {title_columns}{title_dimensions}{names}{title_y}{title_z}{over_by}"
+ "{title_x} {selection}
{groupings}{filters} {title_trendline}"
}
+class SettingsSchema(TypedDict):
+ block: bool
+ editable: bool
+ theme: Literal['light', 'dark', 'classic']
+ refresh_statistics: bool
+ auto_finish: bool
+ render_mode: Literal['auto', 'webgl', 'svg']
+ aggregation: Literal['mean', 'median', 'min', 'max', 'sum', None]
+ title_format: str
+
+
@dataclass
class SettingsStore(DictLike, QtCore.QObject):
settingsChanged = QtCore.pyqtSignal()
@@ -103,6 +114,7 @@ class SettingsStore(DictLike, QtCore.QObject):
block: Setting
editable: Setting
theme: Setting
+ refresh_statistics: Setting
auto_finish: Setting
render_mode: Setting
aggregation: Setting
@@ -175,7 +187,7 @@ def __init__(self, **settings):
self.title_format = Setting(label="title_format",
value=settings['title_format'],
description="title_format",
- dtype=dict,
+ dtype=str,
persist=True)
def reset_to_defaults(self):
@@ -330,13 +342,13 @@ def refresh_statistics(self, force=False):
if force or self.settings.refresh_statistics.value:
df = self.df
self.column_statistics = pd.DataFrame({
- "Type": df.dtypes.astype(str),
- "Count": df.count(),
+ "Type": df.dtypes.astype(str),
+ "Count": df.count(),
"N Unique": nunique(df),
- "Mean": df.mean(numeric_only=True),
- "StdDev": df.std(numeric_only=True),
- "Min": df.min(numeric_only=True),
- "Max": df.max(numeric_only=True),
+ "Mean": df.mean(numeric_only=True),
+ "StdDev": df.std(numeric_only=True),
+ "Min": df.min(numeric_only=True),
+ "Max": df.max(numeric_only=True),
}, index=df.columns
)
@@ -606,7 +618,7 @@ def apply_filters(self):
for ix, filt in enumerate(self.filters):
if filt.enabled and not filt.failed:
try:
- df = df.query(filt.expr)
+ df = df.query(filt.expr, engine='python')
# Handle case where filter returns only one row
if isinstance(df, pd.Series):
df = df.to_frame().T
@@ -826,37 +838,43 @@ def add_dataframe(self, pgdf: Union[DataFrame, PandasGuiDataFrameStore],
def remove_dataframe(self, name_or_index):
self.remove_item(name_or_index)
+ # Handle import (including drag and drop) for all valid file types
@status_message_decorator('Importing file "{path}"...')
def import_file(self, path):
+ filename = os.path.basename(path)
if not os.path.isfile(path):
logger.warning("Path is not a file: " + path)
elif path.endswith(".csv"):
- filename = os.path.split(path)[1].split('.csv')[0]
df = pd.read_csv(path, engine='python')
self.add_dataframe(df, filename)
elif path.endswith(".xlsx"):
- filename = os.path.split(path)[1].split('.csv')[0]
df_dict = pd.read_excel(path, sheet_name=None)
for sheet_name in df_dict.keys():
df_name = f"{filename} - {sheet_name}"
self.add_dataframe(df_dict[sheet_name], df_name)
elif path.endswith(".parquet"):
- filename = os.path.split(path)[1].split('.parquet')[0]
df = pd.read_parquet(path, engine='pyarrow')
self.add_dataframe(df, filename)
elif path.endswith(".json"):
- filename = os.path.split(path)[1].split('.json')[0]
with open(path) as f:
data = json.load(f)
from pandasgui.widgets.json_viewer import JsonViewer
jv = JsonViewer(data)
self.add_item(jv, filename)
elif path.endswith(".pkl"):
- filename = os.path.split(path)[1].split('.pkl')[0]
df = pd.read_pickle(path)
self.add_dataframe(df, filename)
+ elif any([path.endswith(x) for x in [".sqlite", ".sqlite3", ".db", ".db3"]]):
+ import sqlite3
+ with sqlite3.connect(path) as dbcon:
+ tables = list(pd.read_sql_query("SELECT name FROM sqlite_master WHERE type='table';", dbcon)['name'])
+ out = {tbl: pd.read_sql_query(f"SELECT * from {tbl}", dbcon) for tbl in tables}
+ for key, val in out.items():
+ self.add_dataframe(val, f"{filename}-{key}")
+
else:
- logger.warning("Can only import csv / xlsx / parquet. Invalid file: " + path)
+ logger.warning(f"Invalid file: {path}\n"
+ "Can only import the following file types: csv, xlsx, parquet, json, pkl, sqlite")
def get_dataframes(self, names: Union[None, str, list, int] = None):
if type(names) == str:
diff --git a/pandasgui/utility.py b/pandasgui/utility.py
index d6a7cce..2c15085 100644
--- a/pandasgui/utility.py
+++ b/pandasgui/utility.py
@@ -1,7 +1,9 @@
import logging
+import typing
+
import pandas as pd
from PyQt5 import QtWidgets
-from typing import List, Union
+from typing import List, Union, Literal
import sys
import inspect
from collections import OrderedDict
@@ -310,6 +312,25 @@ def clean_dataframe(df, name="DataFrame"):
return df
+def move_columns(df: pd.DataFrame,
+ names: Union[str, List[str]],
+ to: Union[int, Literal['start', 'end']]):
+ if to == 'start':
+ to = 0
+ elif to == 'end':
+ to = len(df.columns) - 1
+
+ if type(names) == str:
+ names = [names]
+
+ cols = df.columns
+ cols_exc_names = [c for c in cols if c not in names]
+ new_cols = cols_exc_names[:to] + names + cols_exc_names[to:]
+ df = df.reindex(new_cols, axis=1)
+
+ return df
+
+
def test_logging():
logger.debug("debug")
logger.info("info")
@@ -424,7 +445,13 @@ def refactor_variable(expr, old_name, new_name):
return astor.code_gen.to_source(tree)
-def get_figure_type(fig):
+def get_figure_type(fig) -> typing.Literal[
+ "plotly",
+ "matplotlib",
+ "bokeh",
+ "altair",
+ "PIL",
+]:
# Plotly
try:
import plotly.basedatatypes
@@ -458,6 +485,13 @@ def get_figure_type(fig):
return "altair"
except ModuleNotFoundError:
pass
+ # PIL
+ try:
+ import PIL.Image
+ if issubclass(type(fig), PIL.Image.Image):
+ return "PIL"
+ except ModuleNotFoundError:
+ pass
return None
diff --git a/pandasgui/widgets/figure_viewer.py b/pandasgui/widgets/figure_viewer.py
index c0d6521..c45a140 100644
--- a/pandasgui/widgets/figure_viewer.py
+++ b/pandasgui/widgets/figure_viewer.py
@@ -46,7 +46,7 @@ def __init__(self, fig=None, store=None):
self.settings().setAttribute(PyQt5.QtWebEngineWidgets.QWebEngineSettings.WebGLEnabled, True)
# https://stackoverflow.com/a/8577226/3620725
- self.temp_file = tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False)
+ self.temp_file = tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding='utf-8')
self.set_figure(fig)
self.setWindowTitle("Plotly Viewer")
@@ -85,7 +85,13 @@ def set_figure(self, fig=None):
tmp = StringIO()
fig.save(tmp, format='html')
html = tmp.getvalue()
-
+ elif fig_type == "PIL":
+ import base64
+ from io import BytesIO
+ tmpfile = BytesIO()
+ fig.save(tmpfile, format='png')
+ encoded = base64.b64encode(tmpfile.getvalue()).decode('utf-8')
+ html = '
'.format(encoded)
else:
raise TypeError
diff --git a/pandasgui/widgets/func_ui.py b/pandasgui/widgets/func_ui.py
index 4e01689..dcb7eeb 100644
--- a/pandasgui/widgets/func_ui.py
+++ b/pandasgui/widgets/func_ui.py
@@ -77,6 +77,19 @@ def __init__(self, arg_name, default_value=None):
self.default_value = default_value
+@dataclass
+class IntArg:
+ arg_name: str
+ default_value: int
+
+ def __init__(self, arg_name, default_value=None):
+ if default_value is None:
+ default_value = None
+
+ self.arg_name = arg_name
+ self.default_value = default_value
+
+
# Display a dropdown to pick a DataFrame name from the list of DataFrames in the GUI
@dataclass
class OtherDataFrameArg:
@@ -123,6 +136,9 @@ def __init__(self,
elif arg_type == bool:
args.append(BooleanArg(arg_name, default_value=arg_default))
+ elif arg_type == int:
+ args.append(IntArg(arg_name, default_value=arg_default))
+
elif arg_type == OtherDataFrame:
args.append(OtherDataFrameArg(arg_name, default_value=arg_default))
@@ -133,7 +149,7 @@ def __init__(self,
self.icon_path = icon_path
name: str
- args: List[Union[ColumnNameArg, ColumnNameListArg, LiteralArg, BooleanArg]]
+ args: List[Union[ColumnNameArg, ColumnNameListArg, LiteralArg, BooleanArg, IntArg]]
label: str
function: Callable
icon_path: str
@@ -152,6 +168,7 @@ def __init__(self, pgdf: PandasGuiDataFrameStore):
self.tree.setDragDropMode(self.tree.DragOnly)
self.tree.setHeaderLabels(['Name', '#Unique', 'Type'])
+
class FuncUi(QtWidgets.QWidget):
valuesChanged = QtCore.pyqtSignal()
itemDropped = QtCore.pyqtSignal()
@@ -447,6 +464,25 @@ def set_schema(self, schema: Schema):
checkbox.stateChanged.emit(Qt.Checked if val else Qt.Unchecked)
checkbox.stateChanged.connect(lambda: self.valuesChanged.emit())
+
+ elif type(arg) == IntArg:
+ spinbox = QtWidgets.QSpinBox()
+
+ spinbox.valueChanged.connect(
+ lambda val, item=item: item.setData(1, Qt.UserRole, val))
+
+ item.treeWidget().setItemWidget(item, 1, spinbox)
+ if arg.arg_name in self.remembered_values.keys():
+ val = self.remembered_values[arg.arg_name]
+ elif arg.arg_name in asdict(SETTINGS_STORE).keys():
+ val = SETTINGS_STORE[arg.arg_name].value
+ else:
+ val = arg.default_value
+
+ spinbox.setValue(val)
+ spinbox.stateChanged.emit(val)
+ spinbox.stateChanged.connect(lambda: self.valuesChanged.emit())
+
elif type(arg) == LiteralArg:
combo_box = QtWidgets.QComboBox()
combo_box.addItems([str(x) for x in arg.values])
diff --git a/setup.py b/setup.py
index e6de155..028ca0c 100644
--- a/setup.py
+++ b/setup.py
@@ -5,7 +5,7 @@
setup(
name="pandasgui",
- version="0.2.13",
+ version="0.2.15",
description="A GUI for Pandas DataFrames.",
author="Adam Rose",
author_email="adrotog@gmail.com",
@@ -16,6 +16,7 @@
long_description_content_type="text/markdown",
exclude_package_data={'': ['.gitignore']},
# Using this instead of MANIFEST.in - https://pypi.org/project/setuptools-git/
+ python_requires=">=3.7",
setup_requires=['setuptools-git'],
install_requires=[
"pandas",
diff --git a/tests/tests.py b/tests/tests.py
index fae7721..1160011 100644
--- a/tests/tests.py
+++ b/tests/tests.py
@@ -14,7 +14,7 @@ def generate_int_data(rows, cols):
def generate_data():
- small_int_path = os.path.join("small_int.csv")
+ small_int_path = "small_int.csv"
large_int_path = "large_int.csv"
small_str_path = "small_str.csv"
large_str_path = "large_str.csv"
@@ -37,9 +37,7 @@ def getObjects(cls):
return objects
# string_data = generate_string_data(rows, cols)
- int_data = pd.read_csv(large_int_path)
-
- # %%
+ int_data = pd.read_csv(generate_int_data(5000000, 50))
for i in range(10):
df = pd.DataFrame(int_data)
@@ -72,9 +70,9 @@ def test_webengine_import():
y=np.random.rand(100),
mode="markers",
marker={
- "size": 30,
- "color": np.random.rand(100),
- "opacity": 0.6,
+ "size": 30,
+ "color": np.random.rand(100),
+ "opacity": 0.6,
"colorscale": "Viridis",
},
)
@@ -92,10 +90,10 @@ def test_inputs():
df = pd.DataFrame(np.random.randn(6, 6), index=ix[:6], columns=ix[:6])
df2 = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6],
- 2: [1, 2, 3], 'c': [4, 5, 6], }).rename(columns={'c': 2})
+ 2: [1, 2, 3], 'c': [4, 5, 6], }).rename(columns={'c': 2})
- from pandasgui.datasets import all_datasets
- show(df, df2, **all_datasets, settings={'block': False})
+ from pandasgui.datasets import pokemon, googleplaystore
+ show(df, df2, pokemon, googleplaystore, settings={'block': False})
def test_code_history():
@@ -126,19 +124,55 @@ def test_code_history():
def test_json():
import requests
from pandasgui import show
- from pandasgui.datasets import all_datasets
+ from pandasgui.datasets import pokemon, googleplaystore
comments = requests.get('https://jsonplaceholder.typicode.com/comments').json()
photos = requests.get('https://jsonplaceholder.typicode.com/photos').json()
- gui = show(comments, photos, **all_datasets, settings={'block': False})
+ gui = show(comments, photos, pokemon, googleplaystore, settings={'block': False})
+
+
+def test_object_types():
+ import requests
+ import pandas as pd
+ from pandasgui import show
+ from pandasgui.datasets import pokemon
+ import numpy as np
+ import plotly.graph_objs as go
+ import io
+ import tempfile
+
+ fig = go.Figure()
+ fig.add_scatter(
+ x=np.random.rand(100),
+ y=np.random.rand(100),
+ mode="markers",
+ marker={
+ "size": 30,
+ "color": np.random.rand(100),
+ "opacity": 0.6,
+ "colorscale": "Viridis",
+ },
+ )
+
+ comments = requests.get('https://jsonplaceholder.typicode.com/comments').json()
+
+ poke_sbuf = io.StringIO()
+ pokemon.head(10).to_csv(poke_sbuf)
+ poke_bbuf = io.BytesIO()
+ pokemon.head(10).to_csv(poke_bbuf)
+ poke_tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False)
+ pokemon.head(10).to_csv(poke_tmp.name)
+ gui = show(comments, pokemon, fig, poke_sbuf, poke_bbuf, poke_tmp.name)
+print('test_json')
test_json()
+print('test_inputs')
test_inputs()
+print('test_code_history')
test_code_history()
# test_webengine_import()
-QtWidgets.QApplication
# iterables = [["bar", "baz", "baz"], ["one", "two"]]
# ix = pd.MultiIndex.from_product(iterables, names=["first", "second"])
# df = pd.DataFrame(np.random.randn(6, 6), index=ix[:6], columns=ix[:6])