diff --git a/src/charts/agent_adoption_chart.py b/src/charts/agent_adoption_chart.py new file mode 100644 index 0000000..8feba4c --- /dev/null +++ b/src/charts/agent_adoption_chart.py @@ -0,0 +1,184 @@ +"""Agent Adoption Survey Comparison Chart + +Grouped horizontal bar chart comparing key enterprise AI adoption metrics +across three major 2025 surveys: LangChain, McKinsey, and PwC. +""" +import matplotlib +matplotlib.use("Agg") + +# Patch matplotlib Path.__deepcopy__ to break Python 3.14 recursion loop +try: + from matplotlib.path import Path + _original_path_deepcopy = Path.__deepcopy__ + def _safe_path_deepcopy(self, memo): + if id(self) in memo: + return memo[id(self)] + memo[id(self)] = self + return self + Path.__deepcopy__ = _safe_path_deepcopy +except Exception: + pass + +import matplotlib.pyplot as plt +import matplotlib.colors as mcolors +import matplotlib.patches as mpatches +from src.data.agent_adoption import agent_survey_data +from src.utils.styling import ( + get_theme, EXPORT_DPI, AGENT_GROWTH, GRAY_DARK, BLACK, WHITE, GRAY_LIGHT, +) + + +def _shade(base_hex: str, factor: float) -> str: + """Lighten or darken a hex color by a given factor (0–1).""" + r, g, b = mcolors.to_rgb(base_hex) + # Blend toward white to lighten + r2 = r + (1.0 - r) * factor + g2 = g + (1.0 - g) * factor + b2 = b + (1.0 - b) * factor + return mcolors.to_hex((r2, g2, b2)) + + +def plot_agent_adoption() -> str: + """Generate grouped horizontal bar chart of survey comparisons.""" + plt.rcParams.update(get_theme()) + fig, ax = plt.subplots(figsize=(14, 8)) + + # ------------------------------------------------------------------ + # Data + # ------------------------------------------------------------------ + lc = agent_survey_data["langchain_2025"] + mc = agent_survey_data["mckinsey_2025"] + pc = agent_survey_data["pwc_2025"] + + # Each row is a comparable category; values are [LangChain, McKinsey, PwC] + # Where a survey has no direct comparable metric, we use None. + categories = [ + "Production\nDeployment", + "Overall\nAI Adoption", + "Budget\nIncrease", + "Scaling\nAgentic AI", + "Productivity\nValue", + ] + + # Values mapped to closest comparable metrics + values = [ + # Production / Deployment + [lc["production"], None, pc["ai_agents_already_adopted"]], + # Overall AI Adoption / Maturity + [lc["observability_implemented"], mc["overall_ai_adoption"], None], + # Budget / Investment Intent + [lc["multi_model_deployments"], None, pc["plan_increase_ai_budgets"]], + # Scaling / Experimentation + [None, mc["agentic_ai_scaling"], None], + # Measurable Value / Productivity + [None, None, pc["measurable_productivity_value"]], + ] + + # Survey identifiers + surveys = [ + "LangChain\n(n=1,340)", + "McKinsey\n(n=1,993)", + "PwC\n(n=308)", + ] + + # Colors: base AGENT_GROWTH with increasing lightness + colors = [ + AGENT_GROWTH, # LangChain — full purple + _shade(AGENT_GROWTH, 0.25), # McKinsey — lighter + _shade(AGENT_GROWTH, 0.50), # PwC — lightest + ] + + # ------------------------------------------------------------------ + # Plotting + # ------------------------------------------------------------------ + n_cats = len(categories) + bar_height = 0.22 + x_positions = [0, 1, 2] # offset within each group + + y_positions = [] + for i in range(n_cats): + base_y = i * 3 # three bars per category + y_positions.append([base_y + off for off in [0.0, 0.22, 0.44]]) + + # Plot bars + for row_idx, (cat, row_vals) in enumerate(zip(categories, values)): + for col_idx, val in enumerate(row_vals): + if val is None: + continue + y = y_positions[row_idx][col_idx] + ax.barh(y, val, height=bar_height, + color=colors[col_idx], + edgecolor=WHITE, linewidth=0.8, + label=surveys[col_idx] if row_idx == 0 else None) + # Value label on bar + ax.text(val + 1.0, y, f"{val:.1f}%", + va="center", fontsize=9, color=GRAY_DARK, + fontweight="bold") + + # Y-axis: category labels centered on each group + group_centers = [i * 3 + 0.22 for i in range(n_cats)] + ax.set_yticks(group_centers) + ax.set_yticklabels(categories, fontsize=11, fontweight="bold") + + # Inset legend-like labels inside each group + legend_y_offset = 0.55 + for col_idx in range(3): + ax.text(-0.5, group_centers[0] + legend_y_offset - col_idx * 0.22, + surveys[col_idx], fontsize=8, color=colors[col_idx], + ha="left", va="center", fontweight="bold") + + # Axis config + ax.set_xlim(0, 105) + ax.set_xlabel("Percentage (%)", fontsize=11, color=GRAY_DARK) + ax.set_xticks(range(0, 106, 10)) + ax.tick_params(axis="x", labelsize=9) + + # Grid + ax.xaxis.grid(True, alpha=0.3, color=GRAY_LIGHT) + ax.yaxis.grid(False) + + # Spine cleanup + ax.spines["top"].set_visible(False) + ax.spines["right"].set_visible(False) + ax.spines["left"].set_color("#cccccc") + ax.spines["bottom"].set_color("#cccccc") + + # Title + ax.set_title( + "Enterprise Agent Adoption — Survey Comparison", + fontsize=18, fontweight="bold", pad=16, color=BLACK, + ) + ax.text( + 0.5, -0.18, + "LangChain (n=1,340) | McKinsey (n=1,993) | PwC (n=308)", + transform=ax.transAxes, + fontsize=11, color=GRAY_DARK, ha="center", + ) + + # Legend + handles = [] + for col_idx in range(3): + handles.append( + mpatches.Rectangle((0, 0), 1, 1, color=colors[col_idx], alpha=1) + ) + ax.legend(handles, surveys, loc="lower right", fontsize=9, + frameon=True, edgecolor="#cccccc") + + # Adjust layout + fig.subplots_adjust(left=0.28, right=0.95, top=0.85, bottom=0.10) + + # Save + out_path = "output/charts/10_agent_adoption.png" + fig.savefig(out_path, dpi=EXPORT_DPI, + facecolor=fig.get_facecolor(), edgecolor="none") + plt.close(fig) + return out_path + + +def main(): + path = plot_agent_adoption() + print(f"Chart saved: {path}") + + +if __name__ == "__main__": + main()