{"spec_id":"parallel-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nparallel-basic: Basic Parallel Coordinates Plot\nLibrary: letsplot 4.11.0 | Python 3.13.14\nQuality: 87/100 | Updated: 2026-07-24\n\"\"\"\n\nimport os\n\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_line,\n    geom_segment,\n    geom_text,\n    ggplot,\n    ggsave,\n    ggsize,\n    labs,\n    scale_alpha_identity,\n    scale_color_manual,\n    scale_size_identity,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n)\n\n\nLetsPlot.setup_html()\n\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\n# Imprint palette positions 1-3 — first series always #009E73\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\"]\n\n# Data - Iris dataset with 4 dimensions\n# Using 30 samples (10 per species) for clarity\ndata = {\n    \"sepal_length\": [\n        5.1,\n        4.9,\n        4.7,\n        4.6,\n        5.0,\n        5.4,\n        4.6,\n        5.0,\n        4.4,\n        4.9,\n        7.0,\n        6.4,\n        6.9,\n        5.5,\n        6.5,\n        5.7,\n        6.3,\n        4.9,\n        6.6,\n        5.2,\n        6.3,\n        5.8,\n        7.1,\n        6.3,\n        6.5,\n        7.6,\n        4.9,\n        7.3,\n        6.7,\n        7.2,\n    ],\n    \"sepal_width\": [\n        3.5,\n        3.0,\n        3.2,\n        3.1,\n        3.6,\n        3.9,\n        3.4,\n        3.4,\n        2.9,\n        3.1,\n        3.2,\n        3.2,\n        3.1,\n        2.3,\n        2.8,\n        2.8,\n        3.3,\n        2.4,\n        2.9,\n        2.7,\n        3.3,\n        2.7,\n        3.0,\n        2.9,\n        3.0,\n        3.0,\n        2.5,\n        2.9,\n        2.5,\n        3.6,\n    ],\n    \"petal_length\": [\n        1.4,\n        1.4,\n        1.3,\n        1.5,\n        1.4,\n        1.7,\n        1.4,\n        1.5,\n        1.4,\n        1.5,\n        4.7,\n        4.5,\n        4.9,\n        4.0,\n        4.6,\n        4.5,\n        4.7,\n        3.3,\n        4.6,\n        3.9,\n        6.0,\n        5.1,\n        5.9,\n        5.6,\n        5.8,\n        6.6,\n        4.5,\n        6.3,\n        5.8,\n        6.1,\n    ],\n    \"petal_width\": [\n        0.2,\n        0.2,\n        0.2,\n        0.2,\n        0.2,\n        0.4,\n        0.3,\n        0.2,\n        0.2,\n        0.1,\n        1.4,\n        1.5,\n        1.5,\n        1.3,\n        1.5,\n        1.3,\n        1.6,\n        1.0,\n        1.3,\n        1.4,\n        2.5,\n        1.9,\n        2.1,\n        1.8,\n        2.2,\n        2.1,\n        1.7,\n        1.8,\n        1.8,\n        2.5,\n    ],\n    \"species\": [\"Setosa\"] * 10 + [\"Versicolor\"] * 10 + [\"Virginica\"] * 10,\n}\n\ndf = pd.DataFrame(data)\n\n# Dimensions to plot (shorter labels to avoid overlap)\ndimensions = [\"sepal_length\", \"sepal_width\", \"petal_length\", \"petal_width\"]\ndim_labels = [\"Sepal\\nLength (cm)\", \"Sepal\\nWidth (cm)\", \"Petal\\nLength (cm)\", \"Petal\\nWidth (cm)\"]\n\n# Normalize each dimension to 0-1 range for fair comparison\ndf_normalized = df.copy()\nfor dim in dimensions:\n    min_val = df[dim].min()\n    max_val = df[dim].max()\n    df_normalized[dim] = (df[dim] - min_val) / (max_val - min_val)\n\n# Convert to long format for parallel coordinates\nline_data = []\nfor idx, row in df_normalized.iterrows():\n    obs_id = idx\n    species = row[\"species\"]\n    for i, dim in enumerate(dimensions):\n        line_data.append({\"x\": i, \"y\": row[dim], \"observation\": obs_id, \"species\": species})\n\nline_df = pd.DataFrame(line_data)\n\n# Fix the Imprint color order explicitly (Setosa always gets brand green,\n# regardless of row/draw order) — see default-style-guide.md \"First series\n# is ALWAYS #009E73\"\nspecies_order = [\"Setosa\", \"Versicolor\", \"Virginica\"]\nline_df[\"species\"] = pd.Categorical(line_df[\"species\"], categories=species_order, ordered=True)\n\n# Setosa shows the sharpest separation on petal dimensions — rendered heavier\n# and more opaque than the other species, and drawn last (on top), so it\n# reads as the visual focal point.\nfocus_species = \"Setosa\"\nline_df[\"line_size\"] = (line_df[\"species\"] == focus_species).map({True: 1.3, False: 0.65})\nline_df[\"line_alpha\"] = (line_df[\"species\"] == focus_species).map({True: 0.9, False: 0.5})\nline_df = pd.concat([line_df[line_df[\"species\"] != focus_species], line_df[line_df[\"species\"] == focus_species]])\n\n# Create axis lines data (vertical lines at each x position)\naxis_data = []\nfor i in range(len(dimensions)):\n    axis_data.append({\"x\": i, \"y\": 0, \"xend\": i, \"yend\": 1})\n\naxis_df = pd.DataFrame(axis_data)\n\n# Horizontal rules tying the axis tops and bottoms together into one frame\nframe_df = pd.DataFrame(\n    {\"x\": [-0.3, -0.3], \"y\": [0, 1], \"xend\": [len(dimensions) - 0.7, len(dimensions) - 0.7], \"yend\": [0, 1]}\n)\n\n# Create label data for dimension names at the bottom\nlabel_data = []\nfor i, label in enumerate(dim_labels):\n    label_data.append({\"x\": i, \"y\": -0.15, \"label\": label})\n\nlabel_df = pd.DataFrame(label_data)\n\n# Create tick labels for each axis (showing original scale) - only min and max\ntick_data = []\nfor i, dim in enumerate(dimensions):\n    min_val = df[dim].min()\n    max_val = df[dim].max()\n    tick_data.append({\"x\": i - 0.08, \"y\": 0, \"label\": f\"{min_val:.1f}\"})\n    tick_data.append({\"x\": i - 0.08, \"y\": 1, \"label\": f\"{max_val:.1f}\"})\n\ntick_df = pd.DataFrame(tick_data)\n\nanyplot_theme = theme(\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    # Only horizontal gridlines — vertical gridlines would create a\n    # phantom-column look that competes with the 4 manually-drawn axis bars.\n    panel_grid_major_x=element_blank(),\n    panel_grid_major_y=element_line(color=INK_SOFT, size=0.2),\n    panel_grid_minor=element_blank(),\n    axis_title=element_blank(),\n    axis_text=element_blank(),\n    axis_ticks=element_blank(),\n    axis_line=element_blank(),\n    plot_title=element_text(color=INK, size=17),\n    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    legend_text=element_text(color=INK_SOFT, size=9),\n    legend_title=element_text(color=INK, size=10),\n)\n\n# Plot\nplot = (\n    ggplot()\n    # Vertical axis lines (theme-adaptive color)\n    + geom_segment(aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\"), data=axis_df, color=INK_SOFT, size=1)\n    # Subtle top/bottom rule tying all axes into one frame\n    + geom_segment(aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\"), data=frame_df, color=INK_SOFT, size=0.5, alpha=0.3)\n    # Data lines connecting observations across dimensions — Setosa rendered\n    # heavier/more opaque (line_size, line_alpha) as the storytelling focal point\n    + geom_line(\n        aes(x=\"x\", y=\"y\", group=\"observation\", color=\"species\", size=\"line_size\", alpha=\"line_alpha\"), data=line_df\n    )\n    # Imprint palette — first series is brand green; size/alpha are literal\n    # values already computed above, not separate legend-worthy aesthetics\n    + scale_color_manual(values=IMPRINT)\n    + scale_size_identity()\n    + scale_alpha_identity()\n    # Dimension labels at the bottom (theme-adaptive color, size matched to base ggsize(800,450))\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=label_df, size=10, color=INK)\n    # Tick value labels on the left side of axes (theme-adaptive color)\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=tick_df, size=8, color=INK_SOFT, hjust=1)\n    # Styling\n    + scale_x_continuous(limits=(-0.4, len(dimensions) - 0.6))\n    + scale_y_continuous(limits=(-0.32, 1.1))\n    + labs(title=\"parallel-basic · python · letsplot · anyplot.ai\", color=\"Species\")\n    + ggsize(800, 450)\n    + anyplot_theme\n)\n\n# Save with theme-named output files\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}