From 21437069c07c7b2b7fb2771a200c983646177723 Mon Sep 17 00:00:00 2001 From: HowToSD Date: Wed, 12 Feb 2025 22:53:33 -0800 Subject: [PATCH] Initial code commit --- .gitignore | 3 + __init__.py | 74 +++++++++++++++++++++++++ credit/credit.md | 30 ++++++++++ modules/matplotlib/common.py | 18 ++++++ modules/matplotlib/mpl_bar.py | 65 ++++++++++++++++++++++ modules/matplotlib/mpl_line.py | 65 ++++++++++++++++++++++ modules/matplotlib/mpl_scatter.py | 65 ++++++++++++++++++++++ modules/matplotlib/util.py | 56 +++++++++++++++++++ modules/pandas/__init__.py | 0 modules/pandas/pandas_head.py | 44 +++++++++++++++ modules/pandas/pandas_join.py | 50 +++++++++++++++++ modules/pandas/pandas_load_csv.py | 45 +++++++++++++++ modules/pandas/pandas_select_columns.py | 47 ++++++++++++++++ modules/pandas/pandas_select_rows.py | 47 ++++++++++++++++ modules/pandas/pandas_show_dataframe.py | 53 ++++++++++++++++++ modules/pandas/pandas_sort.py | 48 ++++++++++++++++ modules/pandas/pandas_summary.py | 44 +++++++++++++++ modules/pandas/pandas_to_string.py | 40 +++++++++++++ web/js/ShowDataFrame.js | 64 +++++++++++++++++++++ 19 files changed, 858 insertions(+) create mode 100644 .gitignore create mode 100644 __init__.py create mode 100644 credit/credit.md create mode 100644 modules/matplotlib/common.py create mode 100644 modules/matplotlib/mpl_bar.py create mode 100644 modules/matplotlib/mpl_line.py create mode 100644 modules/matplotlib/mpl_scatter.py create mode 100644 modules/matplotlib/util.py create mode 100644 modules/pandas/__init__.py create mode 100644 modules/pandas/pandas_head.py create mode 100644 modules/pandas/pandas_join.py create mode 100644 modules/pandas/pandas_load_csv.py create mode 100644 modules/pandas/pandas_select_columns.py create mode 100644 modules/pandas/pandas_select_rows.py create mode 100644 modules/pandas/pandas_show_dataframe.py create mode 100644 modules/pandas/pandas_sort.py create mode 100644 modules/pandas/pandas_summary.py create mode 100644 modules/pandas/pandas_to_string.py create mode 100644 web/js/ShowDataFrame.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cea1d96 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +**/__pycache__/ + diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..33af2a5 --- /dev/null +++ b/__init__.py @@ -0,0 +1,74 @@ +from typing import Dict, Type, TypeVar +from .modules.matplotlib.mpl_bar import MPLBar +from .modules.matplotlib.mpl_line import MPLLine +from .modules.matplotlib.mpl_scatter import MPLScatter +from .modules.pandas.pandas_head import PandasHead +from .modules.pandas.pandas_join import PandasJoin +from .modules.pandas.pandas_load_csv import PandasLoadCSV +from .modules.pandas.pandas_select_columns import PandasSelectColumns +from .modules.pandas.pandas_select_rows import PandasSelectRows +from .modules.pandas.pandas_show_dataframe import PandasShowDataFrame +from .modules.pandas.pandas_sort import PandasSort +from .modules.pandas.pandas_summary import PandasSummary +from .modules.pandas.pandas_to_string import PandasToString + +T = TypeVar("T") + +NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = { + "MPLBar": MPLBar, + "MPLLine": MPLLine, + "MPLScatter": MPLScatter, + "PandasHead": PandasHead, + "PandasJoin": PandasJoin, + "PandasLoadCSV": PandasLoadCSV, + "PandasSelectColumns": PandasSelectColumns, + "PandasSelectRows": PandasSelectRows, + "PandasShowDataFrame": PandasShowDataFrame, + "PandasSort": PandasSort, + "PandasSummary": PandasSummary, + "PandasToString": PandasToString, +} +""" +NODE_CLASS_MAPPINGS (Dict[str, Type[T]]): + A dictionary mapping node names to their corresponding class implementations. + + - "MPLBar": Generates a bar chart from DataFrame data. + - "MPLLine": Generates a line plot from DataFrame data. + - "MPLScatter": Generates a scatter plot from DataFrame data. + - "PandasHead": Handles retrieving the first few rows of a DataFrame. + - "PandasJoin": Provides functionality to join multiple DataFrames. + - "PandasLoadCSV": Loads CSV data into a DataFrame. + - "PandasSelectColumns": Selects specific columns from a DataFrame. + - "PandasSelectRows": Filters specific rows from a DataFrame. + - "PandasShowDataFrame": Displays the content of the DataFrame. + - "PandasSort": Sorts a DataFrame based on given criteria. + - "PandasSummary": Provides a summary of a DataFrame. + - "PandasToString": Converts a DataFrame to a string representation. +""" + +NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = { + "MPLBar": "MPL Bar Chart", + "MPLLine": "MPL Line Plot", + "MPLScatter": "MPL Scatter Plot", + "PandasHead": "Pandas Head", + "PandasJoin": "Pandas Join", + "PandasLoadCSV": "Pandas Load CSV", + "PandasSelectColumns": "Pandas Select Columns", + "PandasSelectRows": "Pandas Select Rows", + "PandasShowDataFrame": "Pandas Show DataFrame", + "PandasSort": "Pandas Sort", + "PandasSummary": "Pandas Summary", + "PandasToString": "Pandas To String", +} +""" +NODE_DISPLAY_NAME_MAPPINGS (Dict[str, str]): + A dictionary mapping node names to user-friendly display names. +""" + +""" +Below two lines were taken from: +https://github.com/pythongosssss/ComfyUI-Custom-Scripts/blob/main/__init__.py +See credit/credit.md for the full license. +""" +WEB_DIRECTORY = "./web" +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"] diff --git a/credit/credit.md b/credit/credit.md new file mode 100644 index 0000000..8b416b7 --- /dev/null +++ b/credit/credit.md @@ -0,0 +1,30 @@ +# Third-party license + +## pythongosssss/ComfyUI-Custom-Scripts +This software is licensed under the following license: +https://github.com/pythongosssss/ComfyUI-Custom-Scripts/blob/main/LICENSE + +Full text: +``` +MIT License + +Copyright (c) 2023 pythongosssss + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` diff --git a/modules/matplotlib/common.py b/modules/matplotlib/common.py new file mode 100644 index 0000000..5c6121c --- /dev/null +++ b/modules/matplotlib/common.py @@ -0,0 +1,18 @@ +def common_input_types(): + """ + Defines the input types for a plot function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "dataframe_json": ("STRING", {"multiscatter": True}), + "x_column_name": ("STRING", {"default": ""}), + "y_column_name": ("STRING", {"default": ""}), + "title": ("STRING", {"default": ""}), + "x_axis_label": ("STRING", {"default": ""}), + "y_axis_label": ("STRING", {"default": ""}), + "x_tick_as_int": ("BOOLEAN", {"default": False}) # To show 2002 instead of 2002.5 on x-axis + } + } diff --git a/modules/matplotlib/mpl_bar.py b/modules/matplotlib/mpl_bar.py new file mode 100644 index 0000000..4cd8bb6 --- /dev/null +++ b/modules/matplotlib/mpl_bar.py @@ -0,0 +1,65 @@ +from typing import Any, Dict +from matplotlib import pyplot as plt +import pandas as pd +from .common import common_input_types +from .util import plot_post_steps + +class MPLBar: + """ + MPLBar: + A class for generating bar charts from a pandas DataFrame. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the `bar_chart` function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return common_input_types() + + RETURN_TYPES: tuple = ("IMAGE",) + FUNCTION: str = "bar_chart" + CATEGORY: str = "Data Analysis" + + def bar_chart(self, + dataframe_json: str, + x_column_name: str, + y_column_name: str, + title: str, + x_axis_label: str, + y_axis_label: str, + x_tick_as_int: bool) -> tuple: + """ + Generates a bar chart from a pandas DataFrame. + + Args: + dataframe_json (str): A JSON string representation of the DataFrame. + x_column_name (str): The column name for the x-axis. + y_column_name (str): The column name for the y-axis. + title (str): The title of the plot. + x_axis_label (str): The label for the x-axis data. + y_axis_label (str): The label for the y-axis data. + x_tick_as_int (bool): If set, use integers for x ticks. For example, + if ticks show floating point numbers for years, e.g. 2002.5, + setting this flag will show integer values instead. + Returns: + tuple: A tuple containing the image tensor representation of the plot. + """ + # Deserialize JSON string to DataFrame + df = pd.read_json(dataframe_json) + + # Create the plot + fig, ax = plt.subplots() + ax.bar(df[x_column_name], df[y_column_name]) + + return plot_post_steps( + fig, + ax, + plt, + title, + x_axis_label, + y_axis_label, + x_tick_as_int) diff --git a/modules/matplotlib/mpl_line.py b/modules/matplotlib/mpl_line.py new file mode 100644 index 0000000..c351752 --- /dev/null +++ b/modules/matplotlib/mpl_line.py @@ -0,0 +1,65 @@ +from typing import Any, Dict +from matplotlib import pyplot as plt +import pandas as pd +from .common import common_input_types +from .util import plot_post_steps + +class MPLLine: + """ + MPLPlotLine: + A class for generating line plots from a pandas DataFrame. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the `line_plot` function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return common_input_types() + + RETURN_TYPES: tuple = ("IMAGE",) + FUNCTION: str = "line_plot" + CATEGORY: str = "Data Analysis" + + def line_plot(self, + dataframe_json: str, + x_column_name: str, + y_column_name: str, + title: str, + x_axis_label: str, + y_axis_label: str, + x_tick_as_int: bool) -> tuple: + """ + Generates a line plot from a pandas DataFrame. + + Args: + dataframe_json (str): A JSON string representation of the DataFrame. + x_column_name (str): The column name for the x-axis. + y_column_name (str): The column name for the y-axis. + title (str): The title of the plot. + x_axis_label (str): The label for the x-axis data. + y_axis_label (str): The label for the y-axis data. + x_tick_as_int (bool): If set, use integers for x ticks. For example, + if ticks show floating point numbers for years, e.g. 2002.5, + setting this flag will show integer values instead. + Returns: + tuple: A tuple containing the image tensor representation of the plot. + """ + # Deserialize JSON string to DataFrame + df = pd.read_json(dataframe_json) + + # Create the plot + fig, ax = plt.subplots() + ax.plot(df[x_column_name], df[y_column_name]) + + return plot_post_steps( + fig, + ax, + plt, + title, + x_axis_label, + y_axis_label, + x_tick_as_int) diff --git a/modules/matplotlib/mpl_scatter.py b/modules/matplotlib/mpl_scatter.py new file mode 100644 index 0000000..21d1ca8 --- /dev/null +++ b/modules/matplotlib/mpl_scatter.py @@ -0,0 +1,65 @@ +from typing import Any, Dict +from matplotlib import pyplot as plt +import pandas as pd +from .common import common_input_types +from .util import plot_post_steps + +class MPLScatter: + """ + MPLPlotScatter: + A class for generating scatter plots from a pandas DataFrame. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the `scatter_plot` function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return common_input_types() + + RETURN_TYPES: tuple = ("IMAGE",) + FUNCTION: str = "scatter_plot" + CATEGORY: str = "Data Analysis" + + def scatter_plot(self, + dataframe_json: str, + x_column_name: str, + y_column_name: str, + title: str, + x_axis_label: str, + y_axis_label: str, + x_tick_as_int: bool) -> tuple: + """ + Generates a scatter plot from a pandas DataFrame. + + Args: + dataframe_json (str): A JSON string representation of the DataFrame. + x_column_name (str): The column name for the x-axis. + y_column_name (str): The column name for the y-axis. + title (str): The title of the plot. + x_axis_label (str): The label for the x-axis data. + y_axis_label (str): The label for the y-axis data. + x_tick_as_int (bool): If set, use integers for x ticks. For example, + if ticks show floating point numbers for years, e.g. 2002.5, + setting this flag will show integer values instead. + Returns: + tuple: A tuple containing the image tensor representation of the plot. + """ + # Deserialize JSON string to DataFrame + df = pd.read_json(dataframe_json) + + # Create the plot + fig, ax = plt.subplots() + ax.plot(df[x_column_name], df[y_column_name]) + + return plot_post_steps( + fig, + ax, + plt, + title, + x_axis_label, + y_axis_label, + x_tick_as_int) diff --git a/modules/matplotlib/util.py b/modules/matplotlib/util.py new file mode 100644 index 0000000..0b2875a --- /dev/null +++ b/modules/matplotlib/util.py @@ -0,0 +1,56 @@ +import io +from typing import Any, Dict +import numpy as np +import matplotlib.figure +import matplotlib.axes +from matplotlib import pyplot as plt +import matplotlib.ticker as mticker +import pandas as pd +from PIL import Image +import torch + + +def plot_post_steps( + fig: matplotlib.figure.Figure, + ax: matplotlib.axes.Axes, + plt: plt, + title: str, + x_axis_label: str, + y_axis_label: str, + x_tick_as_int: bool +) -> tuple: + r"""Plots a post-processed step plot using Matplotlib. + + Args: + fig (matplotlib.figure.Figure): The figure object for the plot. + ax (matplotlib.axes.Axes): The axes object for the plot. + plt (module): The Matplotlib module. + title (str): The title of the plot. + x_axis_label (str): The label for the x-axis. + y_axis_label (str): The label for the y-axis. + x_tick_as_int (bool): If ``True``, use integers for x-axis ticks. + For example, if ticks display floating point numbers (e.g., 2002.5), + setting this flag forces integer values instead. + + Returns: + tuple: A tuple containing the image tensor representation of the plot. + """ + if x_tick_as_int: + ax.xaxis.set_major_locator(mticker.MaxNLocator(integer=True)) + + # Set title and labels + ax.set_title(title) + ax.set_xlabel(x_axis_label) + ax.set_ylabel(y_axis_label) + + # Save the plot to a buffer + buf = io.BytesIO() + fig.savefig(buf, format='PNG') + buf.seek(0) + plt.close(fig) # Close the figure to free memory + + # Load image and convert to tensor + image = Image.open(buf).convert("RGB") + image_tensor = torch.from_numpy(np.array(image)).float() / 255.0 # Normalize to [0,1] + image_tensor = image_tensor.unsqueeze(0) + return (image_tensor,) \ No newline at end of file diff --git a/modules/pandas/__init__.py b/modules/pandas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/modules/pandas/pandas_head.py b/modules/pandas/pandas_head.py new file mode 100644 index 0000000..10df91d --- /dev/null +++ b/modules/pandas/pandas_head.py @@ -0,0 +1,44 @@ +from typing import Any, Dict +import pandas as pd + + +class PandasHead: + """ + PandasHead: + A class for retrieving the first few rows of a pandas DataFrame. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the `head` function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "dataframe_json": ("STRING", {"multiline": True}), + "rows": ("INT", {"default": 10, "min": 0, "max": 50}) + } + } + + RETURN_TYPES: tuple = ("STRING",) + FUNCTION: str = "head" + CATEGORY: str = "Data Analysis" + + def head(self, dataframe_json: str, rows: int) -> tuple: + """ + Returns the first `rows` rows of a pandas DataFrame. + + Args: + dataframe_json (str): A JSON string representation of the DataFrame. + rows (int): The number of rows to retrieve. + + Returns: + tuple: A tuple containing a JSON string of the resulting DataFrame. + """ + # Deserialize JSON string to DataFrame + df = pd.read_json(dataframe_json) + df2 = df.head(rows) + return (df2.to_json(),) diff --git a/modules/pandas/pandas_join.py b/modules/pandas/pandas_join.py new file mode 100644 index 0000000..1c2afde --- /dev/null +++ b/modules/pandas/pandas_join.py @@ -0,0 +1,50 @@ +from typing import Any, Dict +import pandas as pd + + +class PandasJoin: + """ + PandasJoin: + A class for merging two pandas DataFrames based on a common column. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the `sort_rows` function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "left_dataframe_json": ("STRING", {"multiline": True}), + "right_dataframe_json": ("STRING", {"multiline": True}), + "on_column_name": ("STRING", {"default": ""}), + "join_method":(("inner", "left", "right", "outer"),) + } + } + + RETURN_TYPES: tuple = ("STRING",) + FUNCTION: str = "join_dataframes" + CATEGORY: str = "Data Analysis" + + def join_dataframes(self, left_dataframe_json: str, right_dataframe_json: str, on_column_name: str, join_method: str) -> tuple: + """ + Merges two pandas DataFrames based on a common column using the specified join method. + + Args: + left_dataframe_json (str): A JSON string representation of the left DataFrame. + right_dataframe_json (str): A JSON string representation of the right DataFrame. + on_column_name (str): The name of the column to join on. + join_method (str): The type of join to perform ("inner", "left", "right", "outer"). + + Returns: + tuple: A tuple containing a JSON string of the merged DataFrame. + """ + # Deserialize JSON string to DataFrame + df_left = pd.read_json(left_dataframe_json) + df_right = pd.read_json(right_dataframe_json) + + df_out = pd.merge(df_left, df_right, on=on_column_name, how=join_method) + return (df_out.to_json(),) diff --git a/modules/pandas/pandas_load_csv.py b/modules/pandas/pandas_load_csv.py new file mode 100644 index 0000000..fdf76eb --- /dev/null +++ b/modules/pandas/pandas_load_csv.py @@ -0,0 +1,45 @@ +from typing import Any, Dict +import pandas as pd + + +class PandasLoadCSV: + """ + PandasLoadCSV: + A class for loading CSV files into a pandas DataFrame. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the `load_csv` function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "file_path": ("STRING", {"multiline": False}), + } + } + + RETURN_TYPES: tuple = ("STRING",) + FUNCTION: str = "load_csv" + CATEGORY: str = "Data Analysis" + + def load_csv(self, file_path: str) -> tuple: + """ + Loads a CSV file into a pandas DataFrame and converts it to a JSON string. + + Args: + file_path (str): The path to the CSV file. + + Returns: + tuple: A tuple containing a JSON string representation of the DataFrame. + """ + # Read CSV file into DataFrame + df = pd.read_csv(file_path) + + # Convert DataFrame to JSON + result_json = df.to_json() + + return (result_json,) diff --git a/modules/pandas/pandas_select_columns.py b/modules/pandas/pandas_select_columns.py new file mode 100644 index 0000000..da6ce0a --- /dev/null +++ b/modules/pandas/pandas_select_columns.py @@ -0,0 +1,47 @@ +from typing import Any, Dict +import pandas as pd + +class PandasSelectColumns: + """ + PandasSelectColumns: + A class for selecting specific columns from a pandas DataFrame. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the `select_columns` function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "dataframe_json": ("STRING", {"multiline": True}), + "column_names": ("STRING", {"default": ""}) + } + } + + RETURN_TYPES: tuple = ("STRING",) + FUNCTION: str = "select_columns" + CATEGORY: str = "Data Analysis" + + def select_columns(self, dataframe_json: str, column_names: str) -> tuple: + """ + Selects specific columns from a pandas DataFrame. + + Args: + dataframe_json (str): A JSON string representation of the DataFrame. + column_names (str): A comma-separated string of column names to select. + + Returns: + tuple: A tuple containing a JSON string of the DataFrame with selected columns. + """ + # Deserialize JSON string to DataFrame + df = pd.read_json(dataframe_json) + + # Process column names + selected_columns = [col.strip() for col in column_names.split(",")] + df2 = df[selected_columns] + + return (df2.to_json(),) diff --git a/modules/pandas/pandas_select_rows.py b/modules/pandas/pandas_select_rows.py new file mode 100644 index 0000000..7d945fd --- /dev/null +++ b/modules/pandas/pandas_select_rows.py @@ -0,0 +1,47 @@ +import ast +from typing import Any, Dict +import pandas as pd + +class PandasSelectRows: + """ + PandasSelectRows: + A class for selecting specific rows from a pandas DataFrame based on a condition. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the `select_rows` function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "dataframe_json": ("STRING", {"multiline": True}), + "condition": ("STRING", {"default": ""}) + } + } + + RETURN_TYPES: tuple = ("STRING",) + FUNCTION: str = "select_rows" + CATEGORY: str = "Data Analysis" + + def select_rows(self, dataframe_json: str, condition: str) -> tuple: + """ + Selects specific rows from a pandas DataFrame based on a given condition. + + Args: + dataframe_json (str): A JSON string representation of the DataFrame. + condition (str): A string representing the condition to filter rows. + + Returns: + tuple: A tuple containing a JSON string of the filtered DataFrame. + """ + # Deserialize JSON string to DataFrame + df = pd.read_json(dataframe_json) + + # Apply condition + df2 = df.query(condition) + + return (df2.to_json(),) diff --git a/modules/pandas/pandas_show_dataframe.py b/modules/pandas/pandas_show_dataframe.py new file mode 100644 index 0000000..c798152 --- /dev/null +++ b/modules/pandas/pandas_show_dataframe.py @@ -0,0 +1,53 @@ +""" +This code is based on +https://github.com/pythongosssss/ComfyUI-Custom-Scripts/blob/main/py/show_text.py + +See credit/credit.md for the full license. +""" +import pandas as pd + +class PandasShowDataFrame: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "dataframe_json": ("STRING", {"forceInput": True}), + }, + "hidden": { + "unique_id": "UNIQUE_ID", + "extra_pnginfo": "EXTRA_PNGINFO", + }, + } + + INPUT_IS_LIST = True + RETURN_TYPES = ("STRING",) + FUNCTION = "notify" + OUTPUT_NODE = True + OUTPUT_IS_LIST = (True,) + + CATEGORY = "Data Analysis" + + def notify(self, dataframe_json, unique_id=None, extra_pnginfo=None): + text = None + if unique_id is not None and extra_pnginfo is not None: + if not isinstance(extra_pnginfo, list): + print("Error: extra_pnginfo is not a list") + elif ( + not isinstance(extra_pnginfo[0], dict) + or "workflow" not in extra_pnginfo[0] + ): + print("Error: extra_pnginfo[0] is not a dict or missing 'workflow' key") + else: + # Deserialize JSON string to DataFrame + df = pd.read_json(dataframe_json[0]) + text = [str(df)] # wrap in list + workflow = extra_pnginfo[0]["workflow"] + node = next( + (x for x in workflow["nodes"] if str(x["id"]) == str(unique_id[0])), + None, + ) + if node: + node["widgets_values"] = [text] + else: + print("unique_id or extra_pnginfo is None.") + return {"ui": {"text": text}, "result": (text,)} diff --git a/modules/pandas/pandas_sort.py b/modules/pandas/pandas_sort.py new file mode 100644 index 0000000..5a4cbb4 --- /dev/null +++ b/modules/pandas/pandas_sort.py @@ -0,0 +1,48 @@ +from typing import Any, Dict +import pandas as pd + +class PandasSort: + """ + PandasSort: + A class for sorting a pandas DataFrame by a specified column. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the `sort_rows` function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "dataframe_json": ("STRING", {"multiline": True}), + "column_name": ("STRING", {"default": ""}), + "ascending": ("BOOLEAN", {"default": True}) + } + } + + RETURN_TYPES: tuple = ("STRING",) + FUNCTION: str = "sort_rows" + CATEGORY: str = "Data Analysis" + + def sort_rows(self, dataframe_json: str, column_name: str, ascending: bool) -> tuple: + """ + Sorts a pandas DataFrame by the specified column. + + Args: + dataframe_json (str): A JSON string representation of the DataFrame. + column_name (str): The name of the column to sort by. + ascending (bool): Whether to sort in ascending order. + + Returns: + tuple: A tuple containing a JSON string of the sorted DataFrame. + """ + # Deserialize JSON string to DataFrame + df = pd.read_json(dataframe_json) + + # Sort DataFrame + df2 = df.sort_values(by=column_name, ascending=ascending) + + return (df2.to_json(),) diff --git a/modules/pandas/pandas_summary.py b/modules/pandas/pandas_summary.py new file mode 100644 index 0000000..eec9f9c --- /dev/null +++ b/modules/pandas/pandas_summary.py @@ -0,0 +1,44 @@ +from typing import Any, Dict +import pandas as pd + +class PandasSummary: + """ + PandasSummary: + A class for analyzing and summarizing a pandas DataFrame. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the `analyze_dataframe` function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "dataframe_json": ("STRING", {"multiline": True}), + } + } + + RETURN_TYPES: tuple = ("STRING",) + FUNCTION: str = "analyze_dataframe" + CATEGORY: str = "Data Analysis" + + def analyze_dataframe(self, dataframe_json: str) -> tuple: + """ + Analyzes a pandas DataFrame and returns a summary. + + Args: + dataframe_json (str): A JSON string representation of the DataFrame. + + Returns: + tuple: A tuple containing a string representation of the DataFrame summary. + """ + # Deserialize JSON string to DataFrame + df = pd.read_json(dataframe_json) + + # Perform summary operation + summary = df.describe() + + return (str(summary),) diff --git a/modules/pandas/pandas_to_string.py b/modules/pandas/pandas_to_string.py new file mode 100644 index 0000000..8349fad --- /dev/null +++ b/modules/pandas/pandas_to_string.py @@ -0,0 +1,40 @@ +from typing import Any, Dict +import pandas as pd + +class PandasToString: + """ + PandasToString: + A class for converting a pandas DataFrame to a string representation. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the `to_string` function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "dataframe_json": ("STRING", {"multiline": True}), + } + } + + RETURN_TYPES: tuple = ("STRING",) + FUNCTION: str = "to_string" + CATEGORY: str = "Data Analysis" + + def to_string(self, dataframe_json: str) -> tuple: + """ + Converts a pandas DataFrame to a string representation. + + Args: + dataframe_json (str): A JSON string representation of the DataFrame. + + Returns: + tuple: A tuple containing the string representation of the DataFrame. + """ + # Deserialize JSON string to DataFrame + df = pd.read_json(dataframe_json) + return (str(df),) diff --git a/web/js/ShowDataFrame.js b/web/js/ShowDataFrame.js new file mode 100644 index 0000000..81abda3 --- /dev/null +++ b/web/js/ShowDataFrame.js @@ -0,0 +1,64 @@ +/* +This code is based on +https://github.com/pythongosssss/ComfyUI-Custom-Scripts/blob/main/web/js/showText.js + +See credit/credit.md for the full license. +*/ + +import { app } from "../../../scripts/app.js"; +import { ComfyWidgets } from "../../../scripts/widgets.js"; + +// Displays input text on a node +app.registerExtension({ + name: "HowToSD.ShowDataFrame", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "PandasShowDataFrame") { // This needs to match NODE_CLASS_MAPPINGS in Python + function populate(text) { + if (this.widgets) { + for (let i = 1; i < this.widgets.length; i++) { + this.widgets[i].onRemove?.(); + } + this.widgets.length = 1; + } + + const v = [...text]; + if (!v[0]) { + v.shift(); + } + for (const list of v) { + const w = ComfyWidgets["STRING"](this, "text2", ["STRING", { multiline: true }], app).widget; + w.inputEl.readOnly = true; + w.inputEl.style.opacity = 0.6; + w.value = list; + } + + requestAnimationFrame(() => { + const sz = this.computeSize(); + if (sz[0] < this.size[0]) { + sz[0] = this.size[0]; + } + if (sz[1] < this.size[1]) { + sz[1] = this.size[1]; + } + this.onResize?.(sz); + app.graph.setDirtyCanvas(true, false); + }); + } + + // When the node is executed we will be sent the input text, display this in the widget + const onExecuted = nodeType.prototype.onExecuted; + nodeType.prototype.onExecuted = function (message) { + onExecuted?.apply(this, arguments); + populate.call(this, message.text); + }; + + const onConfigure = nodeType.prototype.onConfigure; + nodeType.prototype.onConfigure = function () { + onConfigure?.apply(this, arguments); + if (this.widgets_values?.length) { + populate.call(this, this.widgets_values.slice(+this.widgets_values.length > 1)); + } + }; + } + }, +}); \ No newline at end of file