{"spec_id":"area-mountain-panorama","library":"altair","language":"python","code":"\"\"\" anyplot.ai\narea-mountain-panorama: Mountain Panorama Profile with Labeled Peaks\nLibrary: altair 6.2.2 | Python 3.13.14\nQuality: 84/100 | Updated: 2026-06-30\n\"\"\"\n\nimport importlib\nimport os\nimport sys\n\n\n# Drop script directory from sys.path so `altair` resolves the package, not this file\nsys.path[:] = [p for p in sys.path if os.path.abspath(p or \".\") != os.path.dirname(os.path.abspath(__file__))]\nalt = importlib.import_module(\"altair\")\nnp = importlib.import_module(\"numpy\")\npd = importlib.import_module(\"pandas\")\nfrom PIL import Image\n\n\n# Theme tokens — chrome flips with theme; Imprint palette data colors stay constant\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nBRAND = \"#009E73\"  # Imprint palette position 1 — silhouette fill (single data series)\n\n# Theme-adaptive dusk sky gradient (spec-authorized chrome above the ridgeline)\nSKY_HORIZON = \"#FFC58A\" if THEME == \"light\" else \"#5A3422\"\nSKY_MID = \"#D89AA8\" if THEME == \"light\" else \"#2E1F35\"\nSKY_ZENITH = \"#5C5078\" if THEME == \"light\" else \"#0C0E1A\"\n# Alpenglow rim — warm gold / rose-copper at the sky-to-silhouette boundary\nALPENGLOW = \"#FFBA6A\" if THEME == \"light\" else \"#C88060\"\n\nBASE_ELEV = 2950\n\n# All 16 major Wallis (Valais, CH) summits across a 180° horizontal sweep.\n# left_slope / right_slope: m/degree for piecewise-linear tent flanks.\npeaks = pd.DataFrame(\n    [\n        (\"Weisshorn\", 4506, 9, 280, 200),\n        (\"Zinalrothorn\", 4221, 22, 180, 250),\n        (\"Ober Gabelhorn\", 4063, 30, 220, 180),\n        (\"Dent Blanche\", 4358, 42, 200, 300),\n        (\"Matterhorn\", 4478, 56, 350, 280),  # focal point — steepest flanks\n        (\"Breithorn\", 4164, 72, 150, 160),\n        (\"Pollux\", 4092, 83, 200, 170),\n        (\"Castor\", 4223, 88, 180, 210),\n        (\"Liskamm\", 4527, 97, 200, 180),\n        (\"Monte Rosa\", 4634, 109, 180, 250),\n        (\"Strahlhorn\", 4190, 122, 200, 180),\n        (\"Rimpfischhorn\", 4199, 130, 220, 190),\n        (\"Allalinhorn\", 4027, 137, 180, 200),\n        (\"Alphubel\", 4206, 148, 160, 200),\n        (\"Täschhorn\", 4491, 155, 250, 180),\n        (\"Dom\", 4545, 168, 200, 280),\n    ],\n    columns=[\"name\", \"elevation_m\", \"angle_deg\", \"left_slope\", \"right_slope\"],\n)\n\n# Ridgeline — piecewise-linear tent/triangle functions per spec.\n# Spec explicitly forbids Gaussian/bell-curve bumps; each summit uses two linear\n# flanks meeting at a sharp apex, with asymmetric slope steepness.\nnp.random.seed(42)\nangles = np.linspace(-2, 182, 1500)\n\n# Base ridge always at or above BASE_ELEV — positive-only sinusoidal undulation\nridge_elev = BASE_ELEV + np.maximum(0, 70 * np.sin(angles * 0.12) + 22 * np.sin(angles * 0.47 + 1.1))\n\n# Rocky inter-peak jaggedness: 65 random tent functions (NOT Gaussian)\nfor _ in range(65):\n    pos = np.random.uniform(-2, 182)\n    height = np.random.uniform(60, 320)\n    lslope = np.random.uniform(60, 220)\n    rslope = np.random.uniform(60, 220)\n    tent = BASE_ELEV + height - np.where(angles <= pos, lslope * (pos - angles), rslope * (angles - pos))\n    ridge_elev = np.maximum(ridge_elev, np.maximum(BASE_ELEV, tent))\n\n# Named peaks: steep asymmetric tent functions — sharp apex + linear flanks\nfor _, row in peaks.iterrows():\n    pos, elev = row[\"angle_deg\"], row[\"elevation_m\"]\n    tent = elev - np.where(angles <= pos, row[\"left_slope\"] * (pos - angles), row[\"right_slope\"] * (angles - pos))\n    ridge_elev = np.maximum(ridge_elev, np.maximum(BASE_ELEV, tent))\n\nridge = pd.DataFrame({\"angle_deg\": angles, \"elevation_m\": ridge_elev})\n\n# Four label tiers assigned by round-robin for maximum same-tier angular separation\n# (minimum ~33° within each tier, preventing label collision).\n# TIER_A (5300): Weisshorn(9°), Liskamm(97°), Allalinhorn(137°)\n# TIER_B (5100): Zinalrothorn(22°), Breithorn(72°), Monte Rosa(109°), Alphubel(148°)\n# TIER_C (4900): Ober Gabelhorn(30°), Pollux(83°), Strahlhorn(122°), Täschhorn(155°)\n# TIER_D (4700): Dent Blanche(42°), Castor(88°), Rimpfischhorn(130°), Dom(168°)\n# MATTERHORN SPECIAL (5500): strongest focal accent\nTIER_A, TIER_B, TIER_C, TIER_D, TIER_MAT = 5300, 5100, 4900, 4700, 5500\nlabel_y_map = {\n    \"Weisshorn\": TIER_A,\n    \"Zinalrothorn\": TIER_B,\n    \"Ober Gabelhorn\": TIER_C,\n    \"Dent Blanche\": TIER_D,\n    \"Matterhorn\": TIER_MAT,\n    \"Breithorn\": TIER_B,\n    \"Pollux\": TIER_C,\n    \"Castor\": TIER_D,\n    \"Liskamm\": TIER_A,\n    \"Monte Rosa\": TIER_B,\n    \"Strahlhorn\": TIER_C,\n    \"Rimpfischhorn\": TIER_D,\n    \"Allalinhorn\": TIER_A,\n    \"Alphubel\": TIER_B,\n    \"Täschhorn\": TIER_C,\n    \"Dom\": TIER_D,\n}\npeaks[\"label_y\"] = peaks[\"name\"].map(label_y_map)\npeaks[\"elev_label\"] = peaks[\"elevation_m\"].apply(lambda v: f\"{v} m\")\n\nmatterhorn = peaks[peaks[\"name\"] == \"Matterhorn\"]\nothers = peaks[peaks[\"name\"] != \"Matterhorn\"]\n\n# Coordinate system — only the sky layer carries the explicit scale + axis;\n# other layers share it implicitly via Vega-Lite layer scale resolution.\nX_SCALE = alt.Scale(domain=[0, 180])\nY_SCALE = alt.Scale(domain=[2900, 5800])\nY_AXIS = alt.Axis(values=[3000, 3500, 4000, 4500, 5000])\n\n# Layer 1: dusk sky gradient (vertical linear, zenith → ridge horizon)\nsky_df = pd.DataFrame({\"x_min\": [0], \"x_max\": [180], \"y_min\": [2900], \"y_max\": [5800]})\nsky = (\n    alt.Chart(sky_df)\n    .mark_rect(\n        color={\n            \"x1\": 0,\n            \"y1\": 0,\n            \"x2\": 0,\n            \"y2\": 1,\n            \"gradient\": \"linear\",\n            \"stops\": [\n                {\"offset\": 0.0, \"color\": SKY_ZENITH},\n                {\"offset\": 0.55, \"color\": SKY_MID},\n                {\"offset\": 1.0, \"color\": SKY_HORIZON},\n            ],\n        }\n    )\n    .encode(\n        x=alt.X(\"x_min:Q\", scale=X_SCALE, axis=None),\n        x2=\"x_max:Q\",\n        y=alt.Y(\"y_min:Q\", scale=Y_SCALE, title=\"Elevation (m)\", axis=Y_AXIS),\n        y2=\"y_max:Q\",\n    )\n)\n\n# Layer 2: mountain silhouette — brand-green filled area below the ridgeline\nsilhouette = alt.Chart(ridge).mark_area(color=BRAND, opacity=1.0).encode(x=\"angle_deg:Q\", y=\"elevation_m:Q\")\n\n# Layer 3: alpenglow rim — warm glowing stroke at the sky-to-silhouette boundary\nalpenglow = (\n    alt.Chart(ridge)\n    .mark_line(color=ALPENGLOW, strokeWidth=3.5, opacity=0.88)\n    .encode(x=\"angle_deg:Q\", y=\"elevation_m:Q\")\n)\n\n_tooltip = [alt.Tooltip(\"name:N\", title=\"Peak\"), alt.Tooltip(\"elevation_m:Q\", title=\"Elevation (m)\", format=\",d\")]\n\n# Layers 4–5: leader lines from summit apex to label anchor\nleaders = (\n    alt.Chart(others)\n    .mark_rule(strokeWidth=1.0, opacity=0.55, color=INK_SOFT)\n    .encode(x=\"angle_deg:Q\", y=\"elevation_m:Q\", y2=\"label_y:Q\", tooltip=_tooltip)\n)\nmatterhorn_leader = (\n    alt.Chart(matterhorn)\n    .mark_rule(strokeWidth=2.5, opacity=0.9, color=INK)\n    .encode(x=\"angle_deg:Q\", y=\"elevation_m:Q\", y2=\"label_y:Q\", tooltip=_tooltip)\n)\n\n# Layers 6–7: center-aligned name/elevation labels for all non-Matterhorn peaks\nname_labels = (\n    alt.Chart(others)\n    .mark_text(align=\"center\", baseline=\"bottom\", fontSize=10, fontWeight=\"bold\", color=INK, dy=-22)\n    .encode(x=\"angle_deg:Q\", y=\"label_y:Q\", text=\"name:N\", tooltip=_tooltip)\n)\nelev_labels = (\n    alt.Chart(others)\n    .mark_text(align=\"center\", baseline=\"bottom\", fontSize=10, color=INK_SOFT, dy=-6)\n    .encode(x=\"angle_deg:Q\", y=\"label_y:Q\", text=\"elev_label:N\", tooltip=_tooltip)\n)\n\n# Layers 8–9: Matterhorn focal accent — larger font, heavier weight, composition anchor\nmatterhorn_name = (\n    alt.Chart(matterhorn)\n    .mark_text(align=\"center\", baseline=\"bottom\", fontSize=15, fontWeight=\"bold\", color=INK, dy=-28)\n    .encode(x=\"angle_deg:Q\", y=\"label_y:Q\", text=\"name:N\", tooltip=_tooltip)\n)\nmatterhorn_elev = (\n    alt.Chart(matterhorn)\n    .mark_text(align=\"center\", baseline=\"bottom\", fontSize=12, fontWeight=\"bold\", color=INK_SOFT, dy=-8)\n    .encode(x=\"angle_deg:Q\", y=\"label_y:Q\", text=\"elev_label:N\", tooltip=_tooltip)\n)\n\ntitle_str = \"Wallis Panorama · area-mountain-panorama · python · altair · anyplot.ai\"\nn = len(title_str)\nratio = 67 / n if n > 67 else 1.0\ntitle_fs = max(11, round(16 * ratio))\n\n# height=190: vl-convert with explicit Y_SCALE on the sky anchor layer adds ~47 CSS px\n# of Y overhead per 190 CSS px (source height ≈ 1500px pre-pad, ≤1800px with title).\n# DO NOT increase to ≥210 — overhead scales with height and tips over 1800 source px.\nchart = (\n    (\n        sky\n        + silhouette\n        + alpenglow\n        + leaders\n        + matterhorn_leader\n        + name_labels\n        + elev_labels\n        + matterhorn_name\n        + matterhorn_elev\n    )\n    .properties(\n        width=620,\n        height=190,\n        title=alt.Title(\n            title_str,\n            subtitle=\"Sixteen 4000-m summits along a 180° horizontal sweep, Valais Alps\",\n            subtitleColor=INK_SOFT,\n            subtitleFontSize=13,\n            fontSize=title_fs,\n            anchor=\"start\",\n            offset=12,\n            color=INK,\n        ),\n        background=PAGE_BG,\n    )\n    .configure_view(fill=PAGE_BG, stroke=None)\n    .configure_axis(\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK,\n        gridOpacity=0.0,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        labelFontSize=10,\n        titleFontSize=12,\n        tickSize=5,\n    )\n)\n\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\nchart.save(f\"plot-{THEME}.html\")\n\n# Pad-only to exact 3200×1800 target — altair.md Canvas rule (never crop)\nTW, TH = 3200, 1800\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TW or _h > TH:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TW or _h < TH:\n    _canvas = Image.new(\"RGB\", (TW, TH), PAGE_BG)\n    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n"}