""" Sentiment visualization components using Plotly Creates interactive charts for sentiment analysis """ import plotly.graph_objects as go import plotly.express as px from plotly.subplots import make_subplots import pandas as pd import json from pathlib import Path class SentimentCharts: """ Creates sentiment-related visualizations """ def __init__(self, config_path=None): """ Initialize with configuration Args: config_path: Path to configuration file """ if config_path is None: config_path = Path(__file__).parent.parent / "config" / "viz_config.json" with open(config_path, 'r') as f: self.config = json.load(f) self.sentiment_colors = self.config['color_schemes']['sentiment_polarity'] self.sentiment_order = self.config['sentiment_order'] self.chart_height = self.config['dashboard']['chart_height'] def create_sentiment_pie_chart(self, df, title="Sentiment Distribution"): """ Create pie chart for sentiment distribution Args: df: Sentiment dataframe title: Chart title Returns: plotly.graph_objects.Figure """ sentiment_counts = df['sentiment_polarity'].value_counts() # Order by sentiment_order ordered_sentiments = [s for s in self.sentiment_order if s in sentiment_counts.index] sentiment_counts = sentiment_counts[ordered_sentiments] colors = [self.sentiment_colors.get(s, '#CCCCCC') for s in sentiment_counts.index] fig = go.Figure(data=[go.Pie( labels=sentiment_counts.index, values=sentiment_counts.values, marker=dict(colors=colors), textinfo='label+percent', textposition='auto', hovertemplate='%{label}
Count: %{value}
Percentage: %{percent}' )]) fig.update_layout( title=title, height=self.chart_height, showlegend=True, legend=dict(orientation="v", yanchor="middle", y=0.5, xanchor="left", x=1.05) ) return fig def create_sentiment_bar_chart(self, df, group_by, title="Sentiment Distribution"): """ Create stacked bar chart for sentiment distribution by group Args: df: Sentiment dataframe group_by: Column to group by title: Chart title Returns: plotly.graph_objects.Figure """ # Create pivot table sentiment_pivot = pd.crosstab(df[group_by], df['sentiment_polarity']) # Reorder columns by sentiment_order ordered_columns = [s for s in self.sentiment_order if s in sentiment_pivot.columns] sentiment_pivot = sentiment_pivot[ordered_columns] fig = go.Figure() for sentiment in sentiment_pivot.columns: fig.add_trace(go.Bar( name=sentiment, x=sentiment_pivot.index, y=sentiment_pivot[sentiment], marker_color=self.sentiment_colors.get(sentiment, '#CCCCCC'), hovertemplate='%{x}
%{y} comments' )) fig.update_layout( title=title, xaxis_title=group_by.capitalize(), yaxis_title="Number of Comments", barmode='stack', height=self.chart_height, legend=dict(title="Sentiment", orientation="v", yanchor="top", y=1, xanchor="left", x=1.02) ) return fig def create_sentiment_percentage_bar_chart(self, df, group_by, title="Sentiment Distribution (%)"): """ Create 100% stacked bar chart for sentiment distribution Args: df: Sentiment dataframe group_by: Column to group by title: Chart title Returns: plotly.graph_objects.Figure """ # Create pivot table with percentages sentiment_pivot = pd.crosstab(df[group_by], df['sentiment_polarity'], normalize='index') * 100 # Reorder columns by sentiment_order ordered_columns = [s for s in self.sentiment_order if s in sentiment_pivot.columns] sentiment_pivot = sentiment_pivot[ordered_columns] fig = go.Figure() for sentiment in sentiment_pivot.columns: fig.add_trace(go.Bar( name=sentiment, x=sentiment_pivot.index, y=sentiment_pivot[sentiment], marker_color=self.sentiment_colors.get(sentiment, '#CCCCCC'), hovertemplate='%{x}
%{y:.1f}%' )) fig.update_layout( title=title, xaxis_title=group_by.capitalize(), yaxis_title="Percentage (%)", barmode='stack', height=self.chart_height, yaxis=dict(range=[0, 100]), legend=dict(title="Sentiment", orientation="v", yanchor="top", y=1, xanchor="left", x=1.02) ) return fig def create_sentiment_heatmap(self, df, row_dimension, col_dimension, title="Sentiment Heatmap"): """ Create heatmap showing sentiment distribution across two dimensions Args: df: Sentiment dataframe row_dimension: Row dimension col_dimension: Column dimension title: Chart title Returns: plotly.graph_objects.Figure """ # Create pivot table for negative sentiment percentage negative_sentiments = self.config['negative_sentiments'] df_negative = df[df['sentiment_polarity'].isin(negative_sentiments)] heatmap_data = pd.crosstab( df[row_dimension], df[col_dimension], values=(df['sentiment_polarity'].isin(negative_sentiments)).astype(int), aggfunc='mean' ) * 100 fig = go.Figure(data=go.Heatmap( z=heatmap_data.values, x=heatmap_data.columns, y=heatmap_data.index, colorscale='RdYlGn_r', text=heatmap_data.values.round(1), texttemplate='%{text}%', textfont={"size": 12}, hovertemplate='%{y} - %{x}
Negative: %{z:.1f}%', colorbar=dict(title="Negative %") )) fig.update_layout( title=title, xaxis_title=col_dimension.capitalize(), yaxis_title=row_dimension.capitalize(), height=self.chart_height ) return fig def create_sentiment_timeline(self, df, freq='D', title="Sentiment Over Time"): """ Create line chart showing sentiment trends over time Args: df: Sentiment dataframe with comment_timestamp freq: Frequency for aggregation ('D', 'W', 'M') title: Chart title Returns: plotly.graph_objects.Figure """ if 'comment_timestamp' not in df.columns: return go.Figure().add_annotation( text="No timestamp data available", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False ) df_temp = df.copy() df_temp['date'] = pd.to_datetime(df_temp['comment_timestamp']).dt.to_period(freq).dt.to_timestamp() # Aggregate by date and sentiment timeline_data = df_temp.groupby(['date', 'sentiment_polarity']).size().reset_index(name='count') fig = go.Figure() for sentiment in self.sentiment_order: sentiment_data = timeline_data[timeline_data['sentiment_polarity'] == sentiment] if not sentiment_data.empty: fig.add_trace(go.Scatter( x=sentiment_data['date'], y=sentiment_data['count'], name=sentiment, mode='lines+markers', line=dict(color=self.sentiment_colors.get(sentiment, '#CCCCCC'), width=2), marker=dict(size=6), hovertemplate='%{x}
Count: %{y}' )) fig.update_layout( title=title, xaxis_title="Date", yaxis_title="Number of Comments", height=self.chart_height, legend=dict(title="Sentiment", orientation="v", yanchor="top", y=1, xanchor="left", x=1.02), hovermode='x unified' ) return fig def create_sentiment_score_gauge(self, avg_score, title="Overall Sentiment Score"): """ Create gauge chart for average sentiment score Args: avg_score: Average sentiment score (-2 to +2) title: Chart title Returns: plotly.graph_objects.Figure """ # Normalize score to 0-100 scale normalized_score = ((avg_score + 2) / 4) * 100 fig = go.Figure(go.Indicator( mode="gauge+number+delta", value=normalized_score, domain={'x': [0, 1], 'y': [0, 1]}, title={'text': title, 'font': {'size': 20}}, number={'suffix': '', 'font': {'size': 40}}, gauge={ 'axis': {'range': [0, 100], 'tickwidth': 1, 'tickcolor': "darkblue"}, 'bar': {'color': "darkblue"}, 'bgcolor': "white", 'borderwidth': 2, 'bordercolor': "gray", 'steps': [ {'range': [0, 20], 'color': '#D32F2F'}, {'range': [20, 40], 'color': '#FF6F00'}, {'range': [40, 60], 'color': '#FFB300'}, {'range': [60, 80], 'color': '#7CB342'}, {'range': [80, 100], 'color': '#00C851'} ], 'threshold': { 'line': {'color': "black", 'width': 4}, 'thickness': 0.75, 'value': normalized_score } } )) fig.update_layout( height=300, margin=dict(l=20, r=20, t=60, b=20) ) return fig