{"spec_id":"scatter-connected-temporal","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nscatter-connected-temporal: Connected Scatter Plot with Temporal Path\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 84/100 | Updated: 2026-06-09\n\"\"\"\n\nimport os\nimport re\nimport sys\n\n\n# Remove script's own directory from sys.path so the real pygal package is found first\n_here = os.path.dirname(os.path.abspath(__file__))\nif _here in sys.path:\n    sys.path.remove(_here)\n\nimport cairosvg\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette — amber semantic anchor for key events\nANYPLOT_AMBER = \"#DDCC77\"\n\n# Data — Life expectancy vs GDP per capita for a developing country (1990–2023)\nnp.random.seed(42)\nyears = list(range(1990, 2024))\nn_years = len(years)\n\ngdp_base = 8000\ngdp_growth = np.cumsum(np.random.normal(450, 300, n_years))\ngdp_growth[8:10] -= 1500  # 1998–1999 recession\ngdp_growth[18:20] -= 2000  # 2008–2009 financial crisis\ngdp_growth[30:32] -= 800  # 2020–2021 pandemic\ngdp_per_capita = gdp_base + gdp_growth\ngdp_per_capita = np.maximum(gdp_per_capita, 5000)\n\nle_base = 68.0\nle_growth = np.cumsum(np.random.normal(0.25, 0.12, n_years))\nle_growth[18:20] -= 0.4\nle_growth[30:32] -= 1.2\nlife_expectancy = le_base + le_growth\nlife_expectancy = np.clip(life_expectancy, 64, 82)\n\n# Imprint imprint_seq gradient (#009E73 → #4467A3) for temporal progression\neras = [\n    (\"1990–1997\", 0, 8, \"#009E73\"),\n    (\"1998–2003\", 8, 14, \"#0E937D\"),\n    (\"2004–2009\", 14, 20, \"#1B8886\"),\n    (\"2010–2015\", 20, 26, \"#297D90\"),\n    (\"2016–2019\", 26, 30, \"#367299\"),\n    (\"2020–2023\", 30, 34, \"#4467A3\"),\n]\n\nannotate_years = {1998, 2005, 2008, 2015, 2020}\n\n# Title scaled for 81-char length: round(66 × 67/81) = 55\ntitle = \"Life Expectancy vs GDP · scatter-connected-temporal · python · pygal · anyplot.ai\"\ntitle_font_size = max(44, round(66 * 67 / len(title)))\n\nfont = \"DejaVu Sans, Helvetica, Arial, sans-serif\"\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    guide_stroke_color=INK_MUTED,\n    guide_stroke_dasharray=\"3,5\",\n    colors=(\"#009E73\", \"#0E937D\", \"#1B8886\", \"#297D90\", \"#367299\", \"#4467A3\", ANYPLOT_AMBER, \"#AE3030\", \"#4467A3\"),\n    font_family=font,\n    title_font_family=font,\n    title_font_size=title_font_size,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n    value_label_font_size=44,\n    tooltip_font_size=36,\n    tooltip_font_family=font,\n    opacity=0.92,\n    opacity_hover=1.0,\n    stroke_opacity=0.9,\n    stroke_opacity_hover=1.0,\n)\n\nx_min = float(np.floor(gdp_per_capita.min() / 1000) * 1000)\nx_max = float(np.ceil(gdp_per_capita.max() / 1000) * 1000)\ny_min = float(np.floor(life_expectancy.min()))\ny_max = float(np.ceil(life_expectancy.max()) + 1)\n\nchart = pygal.XY(\n    width=3200,\n    height=1800,\n    style=custom_style,\n    title=title,\n    x_title=\"GDP per Capita (USD)\",\n    y_title=\"Life Expectancy (years)\",\n    show_legend=True,\n    legend_at_bottom=True,\n    legend_at_bottom_columns=3,\n    legend_box_size=28,\n    stroke=True,\n    dots_size=12,\n    show_x_guides=True,\n    show_y_guides=True,\n    x_value_formatter=lambda x: f\"${x / 1000:.0f}k\",\n    value_formatter=lambda y: f\"{y:.1f} yrs\",\n    print_labels=True,\n    print_values=False,\n    margin_bottom=130,\n    margin_left=80,\n    margin_right=80,\n    margin_top=60,\n    range=(y_min, y_max),\n    xrange=(x_min, x_max),\n    x_labels_major_count=7,\n    y_labels_major_count=8,\n    js=[],\n    show_x_labels=True,\n    show_y_labels=True,\n)\n\n# Temporal path as Imprint imprint_seq gradient era segments\nfor era_name, start, end, color in eras:\n    end_idx = min(end + 1, n_years)\n    segment_points = [\n        {\"value\": (float(gdp_per_capita[i]), float(life_expectancy[i])), \"color\": color} for i in range(start, end_idx)\n    ]\n    chart.add(\n        era_name,\n        segment_points,\n        stroke=True,\n        show_dots=True,\n        dots_size=12,\n        stroke_style={\"width\": 5, \"linecap\": \"round\", \"linejoin\": \"round\"},\n    )\n\n# Key years highlighted with amber dots and year labels\nannotated_points = []\nfor yr in sorted(annotate_years):\n    i = yr - 1990\n    annotated_points.append(\n        {\"value\": (float(gdp_per_capita[i]), float(life_expectancy[i])), \"label\": str(yr), \"color\": ANYPLOT_AMBER}\n    )\nchart.add(\"Key years\", annotated_points, stroke=False, dots_size=20)\n\n# Start and end markers\nchart.add(\n    f\"Start ({years[0]})\",\n    [{\"value\": (float(gdp_per_capita[0]), float(life_expectancy[0])), \"label\": \"▶ 1990\", \"color\": \"#AE3030\"}],\n    stroke=False,\n    dots_size=26,\n)\nchart.add(\n    f\"End ({years[-1]})\",\n    [{\"value\": (float(gdp_per_capita[-1]), float(life_expectancy[-1])), \"label\": \"● 2023\", \"color\": \"#4467A3\"}],\n    stroke=False,\n    dots_size=26,\n)\n\n# Patch label text colors for dark-theme legibility before PNG conversion\n# pygal's print_labels text color does not adapt to the dark background via the foreground Style token\n_label_texts = {str(yr) for yr in sorted(annotate_years)} | {\"▶ 1990\", \"● 2023\"}\n\n\ndef _patch_label_colors(svg_str, labels, fill_color):\n    def _fix(m):\n        tag_attrs, content = m.group(1), m.group(2)\n        if not any(lbl in content for lbl in labels):\n            return m.group(0)\n        if \"fill=\" in tag_attrs:\n            tag_attrs = re.sub(r'\\bfill=\"[^\"]*\"', f'fill=\"{fill_color}\"', tag_attrs)\n        else:\n            tag_attrs += f' fill=\"{fill_color}\"'\n        return f\"<text{tag_attrs}>{content}</text>\"\n\n    return re.sub(r\"<text([^>]*)>(.*?)</text>\", _fix, svg_str, flags=re.DOTALL)\n\n\nsvg_data = chart.render()\nsvg_str = _patch_label_colors(svg_data.decode(\"utf-8\"), _label_texts, INK)\ncairosvg.svg2png(bytestring=svg_str.encode(\"utf-8\"), write_to=f\"plot-{THEME}.png\")\nchart.render_to_file(f\"plot-{THEME}.html\")\n"}