from typing import List import plotly.io as pio import plotly.express as px import pandas as pd from utils import TEMP_DIR import os import ast from dotenv import load_dotenv load_dotenv() root_url = os.getenv("ROOT_URL", "") def _write_chart(fig, chart_path, chart_url): """Write a Plotly figure to disk and return a responsive iframe HTML string.""" pio.write_html(fig, chart_path, full_html=False, config={"responsive": True}) return ( 'Please display this iframe: ' '
' '
' ) def llm_chart_data_scrub(data, layout): #Processing data to account for variation from LLM data_list = [] layout_dict = {} if isinstance(data, list): data_list = data else: data_list.append(data) false_replace = [':false', ': false'] false_value = ':False' true_replace = [':true', ': true'] true_value = ':True' data_dict = {} for data_obj in data_list: if isinstance(data_obj, str): data_obj = data_obj.replace("\n", "") for replace in false_replace: data_obj = data_obj.replace(replace, false_value) for replace in true_replace: data_obj = data_obj.replace(replace, true_value) print(data_obj) data_dict = ast.literal_eval(data_obj) else: data_dict = data_obj if layout and isinstance(layout, list): layout_obj = layout[0] else: layout_obj = layout if layout_obj and isinstance(layout_obj, str): for replace in false_replace: layout_obj = layout_obj.replace(replace, false_value) for replace in true_replace: layout_obj = layout_obj.replace(replace, true_value) print(layout_obj) layout_dict = ast.literal_eval(layout_obj) else: layout_dict = layout_obj return data_dict, layout_dict def scatter_chart_fig(df, x_column: List[str], y_column: str, category: str="", trendline: str="", trendline_options: List[dict]=[{}], marginal_x: str="", marginal_y: str="", size: str=""): function_args = {"data_frame":df, "x":x_column, "y":y_column} if category: function_args["color"] = category if trendline: function_args["trendline"] = trendline if marginal_x: function_args["marginal_x"] = marginal_x if marginal_y: function_args["marginal_y"] = marginal_y if size: df.loc[df[size] < 0, size] = 0 function_args["size"] = size if trendline_options: trendline_options_dict = {} if trendline_options and isinstance(trendline_options, list): trendline_options_obj = trendline_options[0] else: trendline_options_obj = trendline_options if trendline_options_obj and isinstance(trendline_options_obj, str): trendline_options_dict = ast.literal_eval(trendline_options_obj) else: trendline_options_dict = trendline_options_obj function_args["trendline_options"] = trendline_options_dict fig = px.scatter(**function_args) return fig def scatter_chart_generation_func(x_column: List[str], y_column: str, session_hash, session_folder, data: List[dict]=[{}], layout: List[dict]=[{}], category: str="", trendline: str="", trendline_options: List[dict]=[{}], marginal_x: str="", marginal_y: str="", size: str="", **kwargs): try: dir_path = TEMP_DIR / str(session_hash) / str(session_folder) chart_path = f'{dir_path}/chart.html' csv_query_path = f'{dir_path}/query.csv' df = pd.read_csv(csv_query_path) initial_graph = scatter_chart_fig(df, x_column=x_column, y_column=y_column, category=category, trendline=trendline, trendline_options=trendline_options, marginal_x=marginal_x, marginal_y=marginal_y, size=size) fig = initial_graph.to_dict() print(data) print(layout) data_dict,layout_dict = llm_chart_data_scrub(data,layout) #Applying stylings and settings generated from LLM if layout_dict: fig["layout"] = layout_dict data_ignore = ["x","y","type"] if size: data_ignore.append("marker") for key, value in data_dict.items(): if key not in data_ignore: for data_item in fig["data"]: data_item[key] = value chart_url = f'{root_url}/gradio_api/file/temp/{session_hash}/{session_folder}/chart.html' return {"reply": _write_chart(fig, chart_path, chart_url)} except Exception as e: print("SCATTER PLOT ERROR") print(e) reply = f"""There was an error generating the Plotly Scatter Plot from {x_column}, {y_column}, {data}, and {layout} The error is {e}, You should probably try again. """ return {"reply": reply} def line_chart_generation_func(x_column: str, y_column: str, session_hash, session_folder, data: List[dict]=[{}], layout: List[dict]=[{}], category: str="", **kwargs): try: dir_path = TEMP_DIR / str(session_hash) / str(session_folder) chart_path = f'{dir_path}/chart.html' csv_query_path = f'{dir_path}/query.csv' df = pd.read_csv(csv_query_path) function_args = {"data_frame":df, "x":x_column, "y":y_column} if category: function_args["color"] = category initial_graph = px.line(**function_args) fig = initial_graph.to_dict() data_dict,layout_dict = llm_chart_data_scrub(data,layout) print(data_dict) print(layout_dict) #Applying stylings and settings generated from LLM if layout_dict: fig["layout"] = layout_dict for key, value in data_dict.items(): if key not in ["x","y","type"]: for data_item in fig["data"]: data_item[key] = value print(fig) chart_url = f'{root_url}/gradio_api/file/temp/{session_hash}/{session_folder}/chart.html' return {"reply": _write_chart(fig, chart_path, chart_url)} except Exception as e: print("LINE CHART ERROR") print(e) reply = f"""There was an error generating the Plotly Line Chart from {x_column}, {y_column}, {data}, and {layout} The error is {e}, You should probably try again. """ return {"reply": reply} def bar_chart_generation_func(x_column: str, y_column: str, session_hash, session_folder, data: List[dict]=[{}], layout: List[dict]=[{}], category: str="", facet_row: str="", facet_col: str="", **kwargs): try: dir_path = TEMP_DIR / str(session_hash) / str(session_folder) chart_path = f'{dir_path}/chart.html' csv_query_path = f'{dir_path}/query.csv' df = pd.read_csv(csv_query_path) function_args = {"data_frame":df, "x":x_column, "y":y_column} if category: function_args["color"] = category if facet_row: function_args["facet_row"] = facet_row if facet_col: function_args["facet_col"] = facet_col initial_graph = px.bar(**function_args) fig = initial_graph.to_dict() data_dict,layout_dict = llm_chart_data_scrub(data,layout) print(data_dict) print(layout_dict) #Applying stylings and settings generated from LLM if layout_dict: fig["layout"] = layout_dict for key, value in data_dict.items(): if key not in ["x","y","type"]: for data_item in fig["data"]: data_item[key] = value print(fig) chart_url = f'{root_url}/gradio_api/file/temp/{session_hash}/{session_folder}/chart.html' return {"reply": _write_chart(fig, chart_path, chart_url)} except Exception as e: print("BAR CHART ERROR") print(e) reply = f"""There was an error generating the Plotly Bar Chart from {x_column}, {y_column}, {data}, and {layout} The error is {e}, You should probably try again. """ return {"reply": reply} def pie_chart_generation_func(values: str, names: str, session_hash, session_folder, data: List[dict]=[{}], layout: List[dict]=[{}], **kwargs): try: dir_path = TEMP_DIR / str(session_hash) / str(session_folder) chart_path = f'{dir_path}/chart.html' csv_query_path = f'{dir_path}/query.csv' df = pd.read_csv(csv_query_path) function_args = {"data_frame":df, "values":values, "names":names} initial_graph = px.pie(**function_args) fig = initial_graph.to_dict() data_dict,layout_dict = llm_chart_data_scrub(data,layout) print(data_dict) print(layout_dict) #Applying stylings and settings generated from LLM if layout_dict: fig["layout"] = layout_dict for key, value in data_dict.items(): if key not in ["x","y","type"]: for data_item in fig["data"]: data_item[key] = value print(fig) chart_url = f'{root_url}/gradio_api/file/temp/{session_hash}/{session_folder}/chart.html' return {"reply": _write_chart(fig, chart_path, chart_url)} except Exception as e: print("PIE CHART ERROR") print(e) reply = f"""There was an error generating the Plotly Pie Chart from {values}, {names}, {data}, and {layout} The error is {e}, You should probably try again. """ return {"reply": reply} def histogram_generation_func(x_column: str, session_hash, session_folder, y_column: str="", data: List[dict]=[{}], layout: List[dict]=[{}], histnorm: str="", category: str="", histfunc: str="", **kwargs): try: dir_path = TEMP_DIR / str(session_hash) / str(session_folder) chart_path = f'{dir_path}/chart.html' csv_query_path = f'{dir_path}/query.csv' df = pd.read_csv(csv_query_path) print(x_column) function_args = {"data_frame":df, "x":x_column} if y_column: function_args["y"] = y_column if histnorm: function_args["histnorm"] = histnorm if category: function_args["color"] = category if histfunc: function_args["histfunc"] = histfunc initial_graph = px.histogram(**function_args) fig = initial_graph.to_dict() data_dict,layout_dict = llm_chart_data_scrub(data,layout) print(data_dict) print(layout_dict) #Applying stylings and settings generated from LLM if layout_dict: fig["layout"] = layout_dict for key, value in data_dict.items(): if key not in ["x","y","type"]: for data_item in fig["data"]: data_item[key] = value print(fig) chart_url = f'{root_url}/gradio_api/file/temp/{session_hash}/{session_folder}/chart.html' return {"reply": _write_chart(fig, chart_path, chart_url)} except Exception as e: print("HISTOGRAM ERROR") print(e) reply = f"""There was an error generating the Plotly Histogram from {x_column}. The error is {e}, You should probably try again. """ return {"reply": reply} def box_chart_generation_func(y_column: str, session_hash, session_folder, x_column: str="", category: str="", layout: List[dict]=[{}], **kwargs): try: dir_path = TEMP_DIR / str(session_hash) / str(session_folder) chart_path = f'{dir_path}/chart.html' csv_query_path = f'{dir_path}/query.csv' df = pd.read_csv(csv_query_path) function_args = {"data_frame": df, "y": y_column} if x_column: function_args["x"] = x_column if category: function_args["color"] = category initial_graph = px.box(**function_args) fig = initial_graph.to_dict() _, layout_dict = llm_chart_data_scrub({}, layout) if layout_dict: fig["layout"] = layout_dict chart_url = f'{root_url}/gradio_api/file/temp/{session_hash}/{session_folder}/chart.html' return {"reply": _write_chart(fig, chart_path, chart_url)} except Exception as e: print("BOX CHART ERROR") print(e) return {"reply": f"There was an error generating the box plot. Error: {e}. You should probably try again."} def correlation_heatmap_func(session_hash, session_folder, columns: List[str]=[], **kwargs): try: dir_path = TEMP_DIR / str(session_hash) / str(session_folder) chart_path = f'{dir_path}/chart.html' csv_query_path = f'{dir_path}/query.csv' df = pd.read_csv(csv_query_path) numeric_df = df[columns].select_dtypes(include='number') if columns else df.select_dtypes(include='number') if numeric_df.shape[1] < 2: return {"reply": "At least two numeric columns are needed for a correlation matrix. Please refine your query to include more numeric columns."} corr = numeric_df.corr().round(3) fig = px.imshow( corr, text_auto='.2f', color_continuous_scale='RdBu_r', zmin=-1, zmax=1, title='Correlation Matrix', aspect='auto', ) fig.update_layout(font=dict(family='Inter, system-ui, sans-serif')) chart_url = f'{root_url}/gradio_api/file/temp/{session_hash}/{session_folder}/chart.html' return {"reply": _write_chart(fig, chart_path, chart_url)} except Exception as e: print("CORRELATION HEATMAP ERROR") print(e) return {"reply": f"There was an error generating the correlation heatmap. Error: {e}. You should probably try again."} def rolling_stats_func(x_column: str, y_column: str, session_hash, session_folder, window: int = 7, stats: List[str] = ["mean"], layout: List[dict] = [{}], category: str = "", **kwargs): try: import plotly.graph_objects as go dir_path = TEMP_DIR / str(session_hash) / str(session_folder) chart_path = f'{dir_path}/chart.html' csv_query_path = f'{dir_path}/query.csv' df = pd.read_csv(csv_query_path) try: df[x_column] = pd.to_datetime(df[x_column]) except Exception: pass df = df.sort_values(x_column) valid_stats = {"mean", "std", "min", "max"} selected_stats = [s for s in stats if s in valid_stats] or ["mean"] fig = go.Figure() groups = df[category].unique().tolist() if category and category in df.columns else [None] for group in groups: group_df = df[df[category] == group] if group is not None else df prefix = f"{group} — " if group is not None else "" fig.add_trace(go.Scatter( x=group_df[x_column].values, y=group_df[y_column].values, mode="lines", name=f"{prefix}{y_column} (raw)", opacity=0.35, line=dict(width=1) )) rolling_obj = group_df[y_column].rolling(window) for stat in selected_stats: rolled = getattr(rolling_obj, stat)() fig.add_trace(go.Scatter( x=group_df[x_column].values, y=rolled.values, mode="lines", name=f"{prefix}Rolling {stat.capitalize()} (w={window})", line=dict(width=2.5) )) fig.update_layout( title=f"Rolling Statistics (window={window}) — {y_column}", xaxis_title=x_column, yaxis_title=y_column, font=dict(family="Inter, system-ui, sans-serif"), legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), ) _, layout_dict = llm_chart_data_scrub({}, layout) if layout_dict: fig.update_layout(**layout_dict) chart_url = f'{root_url}/gradio_api/file/temp/{session_hash}/{session_folder}/chart.html' return {"reply": _write_chart(fig, chart_path, chart_url)} except Exception as e: print("ROLLING STATS ERROR") print(e) return {"reply": f"There was an error generating the rolling statistics chart. Error: {e}. You should probably try again."} def table_generation_func(session_hash, session_folder, **kwargs): print("TABLE GENERATION") try: from html import escape dir_path = TEMP_DIR / str(session_hash) / str(session_folder) csv_query_path = f'{dir_path}/query.csv' df = pd.read_csv(csv_query_path) total_rows = len(df) max_rows = 200 if total_rows > max_rows: df = df.head(max_rows) note = (f'

Showing first {max_rows} of {total_rows} rows' ' — refine your query to see more specific results.

') else: note = '' header_cells = ''.join(f'{escape(str(col))}' for col in df.columns) row_html = [ '' + ''.join(f'{escape(str(val))}' for val in row) + '' for _, row in df.iterrows() ] style = ( '' ) table = ( '
' f'{header_cells}' '' + '\n'.join(row_html) + '' '
' ) return {"reply": style + table + note} except Exception as e: print("TABLE ERROR") print(e) return {"reply": f"There was an error generating the table. Error: {e}. You should probably try again."}