Skip to main content
Glama
chart_generator.py8.91 kB
"""Plotlyグラフ生成モジュール.""" import logging from typing import Optional import pandas as pd import plotly.graph_objects as go from plotly.subplots import make_subplots logger = logging.getLogger(__name__) class ChartGenerator: """Plotlyグラフ生成を行うクラス. モダンなデザインでインタラクティブなグラフを生成します。 """ def __init__(self): """初期化.""" # カラーパレット (Light Theme) self.colors = { "primary": "#A100FF", # パープル "secondary": "#6B00B6", # ディープパープル "accent": "#C966FF", # ライトパープル "background": "#FFFFFF", # ホワイト "plot_bg": "#F8F8F8", # オフホワイト "text": "#333333", # ダークグレー "grid": "#E0E0E0", # ライトグレー } # 視認性が高く目に優しいカラフルなパレット self.color_sequence = [ "#A100FF", # パープル "#3B82F6", # ブルー "#10B981", # エメラルドグリーン "#F59E0B", # オレンジ "#EC4899", # ピンク "#14B8A6", # ティール "#6366F1", # インディゴ "#F43F5E", # ローズ "#8B5CF6", # バイオレット "#06B6D4", # シアン ] def create_bar_chart(self, data: dict, title: str = "頻出キーワード", top_n: int = 20) -> str: """棒グラフ生成(HTML文字列). Args: data: キーワードと頻度の辞書 title: グラフタイトル top_n: 上位N件表示 Returns: str: PlotlyグラフのHTML """ if not data: logger.warning("データが空のため、棒グラフを生成できません") return "" # 上位N件を取得 sorted_data = sorted(data.items(), key=lambda x: x[1], reverse=True)[:top_n] keywords = [item[0] for item in sorted_data] counts = [item[1] for item in sorted_data] # 各バーに異なる色を割り当て(カラーパレットを循環) bar_colors = [self.color_sequence[i % len(self.color_sequence)] for i in range(len(keywords))] # グラフ作成 fig = go.Figure( data=[ go.Bar( x=counts, y=keywords, orientation="h", marker=dict(color=bar_colors, line=dict(color="#FFFFFF", width=1)), hovertemplate="<b>%{y}</b><br>出現回数: %{x}<extra></extra>", ) ] ) # テーマ適用 self._apply_accenture_theme(fig, title) # Y軸を逆順にして上位を上に表示(全てのカテゴリを表示) fig.update_yaxes( autorange="reversed", tickmode="linear", # 全てのカテゴリを表示 dtick=1, # 1つずつ表示 ) logger.info(f"棒グラフ生成完了: {len(keywords)}件") return fig.to_html(include_plotlyjs="cdn", div_id="bar-chart") def create_pie_chart(self, data: dict, title: str = "分布") -> str: """円グラフ生成. Args: data: カテゴリと値の辞書 title: グラフタイトル Returns: str: PlotlyグラフのHTML """ if not data: logger.warning("データが空のため、円グラフを生成できません") return "" labels = list(data.keys()) values = list(data.values()) # グラフ作成 fig = go.Figure( data=[ go.Pie( labels=labels, values=values, hole=0.4, # ドーナツチャート marker=dict(colors=self.color_sequence, line=dict(color=self.colors["background"], width=2)), hovertemplate="<b>%{label}</b><br>件数: %{value}<br>割合: %{percent}<extra></extra>", ) ] ) # テーマ適用 self._apply_accenture_theme(fig, title) # タイトルから一意のdiv_idを生成(日本語を含む場合はハッシュ化) import hashlib title_hash = hashlib.md5(title.encode()).hexdigest()[:8] div_id = f"pie-chart-{title_hash}" logger.info(f"円グラフ生成完了: {len(labels)}カテゴリ (div_id={div_id})") return fig.to_html(include_plotlyjs="cdn", div_id=div_id) def create_radar_chart(self, data: dict, title: str = "軸別スコア") -> str: """レーダーチャート生成. Args: data: カテゴリとスコアの辞書 title: グラフタイトル Returns: str: PlotlyグラフのHTML """ if not data: logger.warning("データが空のため、レーダーチャートを生成できません") return "" categories = list(data.keys()) values = list(data.values()) # 最初の要素を最後にも追加して閉じた図形にする categories_closed = categories + [categories[0]] values_closed = values + [values[0]] # グラフ作成 fig = go.Figure( data=[ go.Scatterpolar( r=values_closed, theta=categories_closed, fill="toself", fillcolor=self.colors["primary"], opacity=0.6, line=dict(color=self.colors["secondary"], width=2), hovertemplate="<b>%{theta}</b><br>スコア: %{r}<extra></extra>", ) ] ) # テーマ適用 fig.update_layout( polar=dict( bgcolor=self.colors["background"], radialaxis=dict( visible=True, range=[0, max(values) * 1.1], gridcolor=self.colors["grid"], gridwidth=1, tickfont=dict(color=self.colors["text"]) ), angularaxis=dict(gridcolor=self.colors["grid"], gridwidth=1, tickfont=dict(color=self.colors["text"])), ), showlegend=False, ) self._apply_accenture_theme(fig, title) logger.info(f"レーダーチャート生成完了: {len(categories)}軸") return fig.to_html(include_plotlyjs="cdn", div_id="radar-chart") def create_heatmap(self, df: pd.DataFrame, title: str = "ヒートマップ") -> str: """ヒートマップ生成. Args: df: DataFrame(行:カテゴリ、列:キーワード) title: グラフタイトル Returns: str: PlotlyグラフのHTML """ if df.empty: logger.warning("データが空のため、ヒートマップを生成できません") return "" # グラフ作成 fig = go.Figure( data=[ go.Heatmap( z=df.values, x=df.columns.tolist(), y=df.index.tolist(), colorscale=[[0, self.colors["background"]], [0.5, self.colors["secondary"]], [1, self.colors["primary"]]], hovertemplate="<b>%{y}</b><br><b>%{x}</b><br>出現回数: %{z}<extra></extra>", ) ] ) # テーマ適用 self._apply_accenture_theme(fig, title) logger.info(f"ヒートマップ生成完了: {df.shape[0]}行 x {df.shape[1]}列") return fig.to_html(include_plotlyjs="cdn", div_id="heatmap") def _apply_accenture_theme(self, fig: go.Figure, title: str) -> None: """モダンテーマ適用 (Light Theme). Args: fig: Plotly Figure title: グラフタイトル """ fig.update_layout( title=dict( text=title, font=dict(size=24, color=self.colors["primary"], family="Montserrat, sans-serif", weight=700), x=0.5, xanchor="center", ), paper_bgcolor=self.colors["background"], plot_bgcolor=self.colors["plot_bg"], font=dict(color=self.colors["text"], family="Noto Sans JP, sans-serif"), hoverlabel=dict(bgcolor=self.colors["primary"], font_size=14, font_family="Noto Sans JP, sans-serif", font_color="white"), margin=dict(l=80, r=80, t=100, b=80), xaxis=dict(gridcolor=self.colors["grid"], gridwidth=1, showline=True, linecolor=self.colors["grid"]), yaxis=dict(gridcolor=self.colors["grid"], gridwidth=1, showline=True, linecolor=self.colors["grid"]), )

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sinjorjob/survey-insight-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server