{"spec_id":"spiral-timeseries","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nspiral-timeseries: Spiral Time Series Chart\nLibrary: seaborn 0.13.2 | Python 3.13.15\nQuality: 91/100 | Updated: 2026-08-18\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent this file from shadowing the installed seaborn package\n_this_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if not (p and os.path.abspath(p) == _this_dir)]\n\nimport matplotlib.cm as cm\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib.collections import LineCollection\nfrom matplotlib.colors import LinearSegmentedColormap, Normalize\n\n\n# Theme tokens (see prompts/default-style-guide.md \"Background\" + \"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\"\nBRAND = \"#009E73\"  # Imprint palette position 1 — ALWAYS first series\n\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.15,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Imprint sequential colormap for continuous data (temperature magnitude)\nimprint_seq = LinearSegmentedColormap.from_list(\"imprint_seq\", [BRAND, \"#4467A3\"])\n\n# Data: 5 years of daily average temperatures (Northern Hemisphere city)\nnp.random.seed(42)\nn_years = 5\ndays_per_year = 365\nn = n_years * days_per_year\n\ndates = pd.date_range(\"2020-01-01\", periods=n, freq=\"D\")\nday_of_year = np.array([d.timetuple().tm_yday for d in dates])\nyear_num = np.array([d.year - 2020 for d in dates])\n\n# Seasonal temperature: peak ~July, trough ~January, with warming trend + noise\ntemperature = 12.0 + 14.0 * np.sin(2 * np.pi * (day_of_year - 80) / 365) + 0.4 * year_num + np.random.normal(0, 2.5, n)\n\n# Archimedean spiral: theta increases 2π per year, r grows with each revolution\ntheta = 2 * np.pi * np.arange(n) / days_per_year\nr_min = 2.0\narm_spacing = 1.9\nr = r_min + arm_spacing * (theta / (2 * np.pi))\n\n# Build colored line segments for the spiral\npoints = np.column_stack([theta, r]).reshape(-1, 1, 2)\nsegments = np.concatenate([points[:-1], points[1:]], axis=1)\n\nnorm = Normalize(vmin=temperature.min(), vmax=temperature.max())\nlc = LineCollection(segments, cmap=imprint_seq, norm=norm, linewidth=4.5, alpha=0.92, zorder=3)\nlc.set_array(temperature[:-1])\n\n# Plot: square canvas, spiral (left) beside two seaborn-native supplementary panels (right)\nfig = plt.figure(figsize=(6, 6), dpi=400, facecolor=PAGE_BG)\ngs = fig.add_gridspec(\n    2,\n    2,\n    width_ratios=[1.35, 1],\n    height_ratios=[1, 1],\n    left=0.11,\n    right=0.97,\n    top=0.90,\n    bottom=0.08,\n    wspace=0.38,\n    hspace=0.55,\n)\n\nax = fig.add_subplot(gs[:, 0], projection=\"polar\")\nax_trend = fig.add_subplot(gs[0, 1])\nax_season = fig.add_subplot(gs[1, 1])\n\n# January at top, spiral growing clockwise\nax.set_facecolor(PAGE_BG)\nax.set_theta_zero_location(\"N\")\nax.set_theta_direction(-1)\nax.add_collection(lc)\n\n# Radial limits\nr_outer = r_min + arm_spacing * n_years + 0.8\nax.set_ylim(0, r_outer)\n\n# Month angular ticks and labels around the outer ring\nmonth_names = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"]\nmonth_start_days = [1, 32, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335]\nmonth_angles_deg = [360.0 * (d - 1) / 365.0 for d in month_start_days]\n\nax.set_thetagrids(month_angles_deg, labels=month_names)\nfor label in ax.get_xticklabels():\n    label.set_color(INK_SOFT)\n    label.set_fontsize(12)\nax.tick_params(axis=\"x\", pad=14, length=0)\n\n# Remove radial tick marks and labels (they clutter the spiral)\nax.set_rticks([])\nax.set_yticklabels([])\n\n# Year labels just outside the outer edge of each spiral arm (past Jan 1, clockwise);\n# a background swatch means contrast never depends on the local spiral color.\nyear_label_angle = np.radians(10)\nfor yr in range(n_years):\n    yr_r = r_min + arm_spacing * yr + 0.4\n    ax.text(\n        year_label_angle,\n        yr_r,\n        str(2020 + yr),\n        ha=\"left\",\n        va=\"center\",\n        fontsize=10,\n        color=INK,\n        fontweight=\"bold\",\n        zorder=6,\n        bbox={\"boxstyle\": \"round,pad=0.12\", \"facecolor\": PAGE_BG, \"edgecolor\": \"none\", \"alpha\": 0.85},\n    )\n\n# Show only spoke grid lines (months), hide concentric r-circles\nax.yaxis.grid(False)\nax.xaxis.grid(True, alpha=0.15, color=INK, linewidth=0.8)\nax.spines[\"polar\"].set_visible(False)\n\n# Colorbar below the spiral — horizontal, so it never competes with the\n# month-label ring on the right side of the wheel (the recurring \"Apr\" clip)\nsm = cm.ScalarMappable(cmap=imprint_seq, norm=norm)\nsm.set_array([])\ncbar = fig.colorbar(sm, ax=ax, orientation=\"horizontal\", pad=0.1, shrink=0.75, aspect=28)\ncbar.set_label(\"Daily Avg. Temp. (°C)\", fontsize=9, color=INK, labelpad=6)\ncbar.ax.tick_params(labelsize=8, colors=INK_SOFT)\ncbar.outline.set_edgecolor(INK_SOFT)\n\n# Supplementary panel: annual warming trend (seaborn regplot over all daily obs.)\nfractional_year = 2020 + year_num + (day_of_year - 1) / 365\nsns.regplot(\n    x=fractional_year,\n    y=temperature,\n    ax=ax_trend,\n    color=BRAND,\n    scatter_kws={\"alpha\": 0.15, \"s\": 8},\n    line_kws={\"linewidth\": 2.5},\n)\nax_trend.set_title(\"Annual Warming Trend\", fontsize=10, color=INK, pad=6)\nax_trend.set_xlabel(\"Year\", fontsize=9, color=INK)\nax_trend.set_ylabel(\"Daily Avg. Temp. (°C)\", fontsize=9, color=INK)\nax_trend.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\nax_trend.set_xticks([2020, 2021, 2022, 2023, 2024])\nsns.despine(ax=ax_trend)\nax_trend.yaxis.grid(True, alpha=0.15, linewidth=0.6, color=INK)\n\n# Supplementary panel: seasonal cycle (seaborn lineplot aggregates mean ± sd across years)\nsns.lineplot(x=day_of_year, y=temperature, ax=ax_season, color=BRAND, errorbar=\"sd\", linewidth=2.5)\nax_season.set_title(\"Seasonal Cycle\", fontsize=10, color=INK, pad=6)\nax_season.set_xlabel(\"Month\", fontsize=9, color=INK)\nax_season.set_ylabel(\"Daily Avg. Temp. (°C)\", fontsize=9, color=INK)\nax_season.set_xticks(month_start_days[::2])\nax_season.set_xticklabels(month_names[::2])\nax_season.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\nsns.despine(ax=ax_season)\nax_season.yaxis.grid(True, alpha=0.15, linewidth=0.6, color=INK)\n\n# Title\nfig.suptitle(\"spiral-timeseries · python · seaborn · anyplot.ai\", fontsize=13, fontweight=\"medium\", color=INK, y=0.97)\n\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}