{"spec_id":"parallel-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nparallel-basic: Basic Parallel Coordinates Plot\nLibrary: altair 6.2.2 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-07-24\n\"\"\"\n\nimport importlib\nimport os\nimport sys\n\nfrom PIL import Image\n\n\n# This file is named 'altair.py'. Remove the script directory (and '') from sys.path\n# before loading the altair library to prevent this file from being imported as altair.\n_script_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if p not in (\"\", _script_dir)]\n\nalt = importlib.import_module(\"altair\")\nnp = importlib.import_module(\"numpy\")\npd = importlib.import_module(\"pandas\")\n\n# Theme tokens\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\"\n\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\"]\n\n# Data - Iris-like dataset with 4 dimensions and 3 species\nnp.random.seed(42)\n\nn_per_species = 50\nspecies_names = [\"Setosa\", \"Versicolor\", \"Virginica\"]\ndimensions = [\n    \"Sepal Length (cm)\",\n    \"Sepal Width (cm)\",\n    \"Petal Length (cm)\",\n    \"Petal Width (cm)\",\n    \"Petal Area (cm2)\",\n    \"Sepal Aspect Ratio\",\n]\n\ndata = []\nfor i, sp in enumerate(species_names):\n    sepal_length = np.random.normal([5.0, 5.9, 6.6][i], 0.35, n_per_species)\n    sepal_width = np.random.normal([3.4, 2.8, 3.0][i], 0.38, n_per_species)\n    petal_length = np.random.normal([1.5, 4.3, 5.5][i], 0.17 + i * 0.25, n_per_species)\n    petal_width = np.random.normal([0.2, 1.3, 2.0][i], 0.1 + i * 0.15, n_per_species)\n    petal_area = petal_length * petal_width\n    sepal_aspect_ratio = sepal_length / sepal_width\n    for j in range(n_per_species):\n        data.append(\n            {\n                \"Species\": sp,\n                \"Sepal Length (cm)\": round(sepal_length[j], 2),\n                \"Sepal Width (cm)\": round(sepal_width[j], 2),\n                \"Petal Length (cm)\": round(petal_length[j], 2),\n                \"Petal Width (cm)\": round(petal_width[j], 2),\n                \"Petal Area (cm2)\": round(petal_area[j], 2),\n                \"Sepal Aspect Ratio\": round(sepal_aspect_ratio[j], 2),\n                \"id\": i * n_per_species + j,\n            }\n        )\n\ndf = pd.DataFrame(data)\n\n# Normalize values to 0-1 range for fair comparison across axes\nfor dim in dimensions:\n    min_val = df[dim].min()\n    max_val = df[dim].max()\n    df[f\"{dim}_norm\"] = (df[dim] - min_val) / (max_val - min_val)\n\n# Long format retaining original values for tooltips\ndf_long = df.melt(\n    id_vars=[\"id\", \"Species\"] + dimensions,\n    value_vars=[f\"{d}_norm\" for d in dimensions],\n    var_name=\"Dimension\",\n    value_name=\"Normalized Value\",\n)\ndf_long[\"Dimension\"] = df_long[\"Dimension\"].str.replace(\"_norm\", \"\")\n\n# Interactive selection: click a species in the legend to highlight/dim\nspecies_select = alt.selection_point(fields=[\"Species\"], bind=\"legend\", empty=True)\n\n# Plot\nspec = (\n    alt.Chart(df_long)\n    .mark_line(strokeWidth=2.0)\n    .encode(\n        x=alt.X(\n            \"Dimension:N\",\n            sort=dimensions,\n            axis=alt.Axis(labelAngle=-20, labelFontSize=13, titleFontSize=17, title=None, labelPadding=10),\n        ),\n        y=alt.Y(\n            \"Normalized Value:Q\",\n            scale=alt.Scale(domain=[0, 1]),\n            axis=alt.Axis(labelFontSize=14, titleFontSize=17, title=\"Normalized Value\", tickCount=5),\n        ),\n        detail=\"id:N\",\n        color=alt.Color(\n            \"Species:N\",\n            scale=alt.Scale(domain=species_names, range=IMPRINT),\n            legend=alt.Legend(\n                title=\"Species\",\n                titleFontSize=17,\n                labelFontSize=15,\n                symbolSize=170,\n                symbolStrokeWidth=3,\n                orient=\"right\",\n                padding=12,\n                labelLimit=200,\n            ),\n        ),\n        opacity=alt.condition(species_select, alt.value(0.40), alt.value(0.06)),\n        tooltip=[\n            alt.Tooltip(\"Species:N\"),\n            alt.Tooltip(\"Sepal Length (cm):Q\", format=\".2f\"),\n            alt.Tooltip(\"Sepal Width (cm):Q\", format=\".2f\"),\n            alt.Tooltip(\"Petal Length (cm):Q\", format=\".2f\"),\n            alt.Tooltip(\"Petal Width (cm):Q\", format=\".2f\"),\n            alt.Tooltip(\"Petal Area (cm2):Q\", format=\".2f\"),\n            alt.Tooltip(\"Sepal Aspect Ratio:Q\", format=\".2f\"),\n        ],\n    )\n    .properties(\n        width=580,\n        height=300,\n        background=PAGE_BG,\n        title=alt.Title(\n            \"parallel-basic · python · altair · anyplot.ai\",\n            subtitle=\"Setosa separates cleanly on petal traits, while Versicolor and Virginica overlap on sepal traits\",\n            fontSize=24,\n            subtitleFontSize=14,\n            subtitleColor=INK_SOFT,\n            anchor=\"middle\",\n        ),\n    )\n    .add_params(species_select)\n)\n\nchart = (\n    spec.configure_view(fill=PAGE_BG, strokeWidth=0)\n    .configure_axis(\n        domainColor=INK_SOFT, tickColor=INK_SOFT, gridColor=INK, gridOpacity=0.10, labelColor=INK_SOFT, titleColor=INK\n    )\n    .configure_title(color=INK)\n    .configure_legend(\n        fillColor=ELEVATED_BG,\n        strokeColor=INK_SOFT,\n        strokeWidth=0.5,\n        cornerRadius=4,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n    )\n)\n\n# Save\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\n# PAD-only to canonical target (do NOT crop — cropping clips title/axis labels)\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}x{_h}, exceeds target {TW}x{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\nchart.save(f\"plot-{THEME}.html\")\n"}