{"spec_id":"scatter-connected-temporal","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nscatter-connected-temporal: Connected Scatter Plot with Temporal Path\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-06-09\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    arrow,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_path,\n    geom_point,\n    geom_segment,\n    geom_text,\n    ggplot,\n    ggsave,\n    ggsize,\n    labs,\n    layer_tooltips,\n    scale_color_gradient,\n    scale_fill_gradient,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\n\n\nLetsPlot.setup_html()\n\n# Theme tokens — Imprint palette, theme-adaptive chrome\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nGRID = \"rgba(26,26,23,0.12)\" if THEME == \"light\" else \"rgba(240,239,232,0.12)\"\n\n# Imprint sequential colormap: brand green (early) → blue (recent)\nSEQ_LOW = \"#009E73\"  # Imprint position 1 — start of temporal path\nSEQ_HIGH = \"#4467A3\"  # Imprint position 3 — end of temporal path\n\n# Data — Phillips curve dynamics, unemployment vs inflation 1990–2023\nnp.random.seed(42)\nyears = np.arange(1990, 2024)\nn = len(years)\n\nunemployment = np.concatenate(\n    [\n        np.linspace(5.6, 4.0, 10) + np.random.randn(10) * 0.3,  # 1990s decline\n        np.linspace(4.0, 6.3, 4) + np.random.randn(4) * 0.2,  # 2001 recession\n        np.linspace(6.3, 4.4, 6) + np.random.randn(6) * 0.2,  # mid-2000s recovery\n        np.linspace(4.4, 10.0, 3) + np.random.randn(3) * 0.3,  # 2008 crisis\n        np.linspace(10.0, 3.5, 11) + np.random.randn(11) * 0.3,  # long recovery\n    ]\n)\ninflation = np.concatenate(\n    [\n        np.linspace(5.4, 2.3, 10) + np.random.randn(10) * 0.4,  # 1990s disinflation\n        np.linspace(2.3, 1.6, 4) + np.random.randn(4) * 0.3,  # low inflation\n        np.linspace(1.6, 3.8, 6) + np.random.randn(6) * 0.3,  # rising\n        np.linspace(3.8, -0.4, 3) + np.random.randn(3) * 0.4,  # deflation scare\n        np.linspace(-0.4, 6.5, 11) + np.random.randn(11) * 0.5,  # recovery to post-covid\n    ]\n)\n\ndf = pd.DataFrame(\n    {\n        \"unemployment\": unemployment,\n        \"inflation\": inflation,\n        \"year\": years,\n        \"year_label\": [str(y) for y in years],\n        \"time_idx\": np.arange(n),\n    }\n)\n\n# Annotate key economic turning points\nkey_years = {1990, 2000, 2007, 2009, 2020, 2023}\ndf_labels = df[df[\"year\"].isin(key_years)].copy()\n\nnudge_map = {\n    1990: (0.35, 0.7),\n    2000: (0.35, -0.7),\n    2007: (0.35, -0.7),\n    2009: (-0.55, 0.7),\n    2020: (-0.35, 0.7),\n    2023: (0.35, 0.7),\n}\n\ndf_endpoints = df[df[\"year\"].isin([1990, 2023])].copy()\ndf_labels[\"label_x\"] = df_labels.apply(lambda r: r[\"unemployment\"] + nudge_map.get(r[\"year\"], (0, 0))[0], axis=1)\ndf_labels[\"label_y\"] = df_labels.apply(lambda r: r[\"inflation\"] + nudge_map.get(r[\"year\"], (0, 0))[1], axis=1)\n\n# Direction arrow at terminal segment of the path\nlast = df.iloc[-1]\nprev = df.iloc[-2]\narrow_df = pd.DataFrame(\n    {\"x\": [prev[\"unemployment\"]], \"y\": [prev[\"inflation\"]], \"xend\": [last[\"unemployment\"]], \"yend\": [last[\"inflation\"]]}\n)\n\ntitle = \"scatter-connected-temporal · python · letsplot · anyplot.ai\"\n\n# Plot\nplot = (\n    ggplot(df, aes(x=\"unemployment\", y=\"inflation\"))\n    + geom_path(aes(color=\"time_idx\"), size=1.5, alpha=0.75, tooltips=\"none\")\n    + geom_segment(\n        data=arrow_df,\n        mapping=aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\"),\n        color=SEQ_HIGH,\n        size=2.2,\n        arrow=arrow(angle=25, length=10, type=\"closed\"),\n    )\n    + geom_point(\n        aes(fill=\"time_idx\"),\n        color=PAGE_BG,\n        size=3.5,\n        stroke=1.0,\n        shape=21,\n        alpha=0.9,\n        tooltips=layer_tooltips()\n        .line(\"Year|@year\")\n        .line(\"Unemployment|@{unemployment}{.1f}%\")\n        .line(\"Inflation|@{inflation}{.1f}%\"),\n    )\n    + geom_point(\n        data=df_endpoints,\n        mapping=aes(x=\"unemployment\", y=\"inflation\", fill=\"time_idx\"),\n        color=INK,\n        size=6.0,\n        stroke=1.8,\n        shape=21,\n        alpha=1.0,\n    )\n    + geom_text(\n        data=df_labels,\n        mapping=aes(x=\"label_x\", y=\"label_y\", label=\"year_label\"),\n        size=6,\n        color=INK,\n        family=\"monospace\",\n        fontface=\"bold\",\n    )\n    + scale_color_gradient(\n        low=SEQ_LOW, high=SEQ_HIGH, name=\"Year\", breaks=[0, (n - 1) / 2, n - 1], labels=[\"1990\", \"2006\", \"2023\"]\n    )\n    + scale_fill_gradient(low=SEQ_LOW, high=SEQ_HIGH, guide=\"none\")\n    + scale_x_continuous(expand=[0.06, 0])\n    + scale_y_continuous(expand=[0.08, 0])\n    + labs(\n        x=\"Unemployment Rate (%)\",\n        y=\"Inflation Rate (%)\",\n        title=title,\n        subtitle=\"Phillips Curve: unemployment vs inflation, 1990–2023\",\n    )\n    + ggsize(800, 450)\n    + theme_minimal()\n    + theme(\n        axis_text=element_text(size=10, color=INK_SOFT),\n        axis_title=element_text(size=12, color=INK),\n        plot_title=element_text(size=16, color=INK),\n        plot_subtitle=element_text(size=10, color=INK_SOFT),\n        legend_text=element_text(size=10, color=INK_SOFT),\n        legend_title=element_text(size=12, color=INK),\n        panel_grid_major=element_line(color=GRID, size=0.3),\n        panel_grid_minor=element_blank(),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        panel_border=element_blank(),\n        plot_margin=[20, 20, 10, 10],\n    )\n)\n\n# Save — PNG at 3200×1800 (800×450 × scale=4), plus interactive HTML\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}