{"spec_id":"scatter-matrix","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nscatter-matrix: Scatter Plot Matrix\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 66/100 | Updated: 2026-05-09\n\"\"\"\n\nfrom io import BytesIO\n\nimport cairosvg\nimport numpy as np\nimport pygal\nfrom PIL import Image, ImageDraw, ImageFont\nfrom pygal.style import Style\n\n\n# Data - Iris-like dataset with 4 variables\nnp.random.seed(42)\nn_samples = 100\n\n# Create correlated multivariate data with both positive and negative correlations\nbase = np.random.randn(n_samples)\nsepal_length = 5.8 + base * 0.8 + np.random.randn(n_samples) * 0.3\nsepal_width = 3.0 - base * 0.4 + np.random.randn(n_samples) * 0.25  # Negative correlation with sepal_length\npetal_length = 3.8 + base * 1.5 + np.random.randn(n_samples) * 0.4\npetal_width = 1.2 + base * 0.6 + np.random.randn(n_samples) * 0.2\n\nvariables = {\n    \"Sepal Length\": sepal_length,\n    \"Sepal Width\": sepal_width,\n    \"Petal Length\": petal_length,\n    \"Petal Width\": petal_width,\n}\nvar_names = list(variables.keys())\nn_vars = len(var_names)\n\n# Style configuration with improved transparency for overlapping points\ncustom_style = Style(\n    background=\"white\",\n    plot_background=\"#f8f8f8\",\n    foreground=\"#333\",\n    foreground_strong=\"#333\",\n    foreground_subtle=\"#666\",\n    colors=(\"#306998\", \"#FFD43B\", \"#4B8BBE\", \"#FFE873\"),\n    title_font_size=28,\n    label_font_size=18,\n    major_label_font_size=16,\n    legend_font_size=16,\n    value_font_size=14,\n    opacity=0.55,\n    opacity_hover=0.85,\n)\n\n# Canvas dimensions\ntotal_width = 3600\ntotal_height = 3600\nmargin_top = 120\nmargin_bottom = 120\nmargin_left = 120\nmargin_right = 50\nplot_area_width = total_width - margin_left - margin_right\nplot_area_height = total_height - margin_top - margin_bottom\ncell_size = min(plot_area_width, plot_area_height) // n_vars\ngap = 10\n\n# Create composite image\ncomposite = Image.new(\"RGB\", (total_width, total_height), \"white\")\n\n# Render each cell chart and paste into composite\nfor i in range(n_vars):\n    for j in range(n_vars):\n        x_pos = margin_left + j * cell_size + gap // 2\n        y_pos = margin_top + i * cell_size + gap // 2\n        inner_size = cell_size - gap\n\n        var_x = var_names[j]\n        var_y = var_names[i]\n\n        if i == j:\n            # Diagonal: Histogram\n            chart = pygal.Histogram(\n                width=inner_size,\n                height=inner_size,\n                style=custom_style,\n                show_legend=False,\n                show_x_labels=(i == n_vars - 1),\n                show_y_labels=(j == 0),\n                x_label_rotation=0,\n                show_minor_x_labels=False,\n                show_minor_y_labels=False,\n                margin_top=8,\n                margin_right=8,\n                margin_bottom=40 if i == n_vars - 1 else 8,\n                margin_left=70 if j == 0 else 8,\n                spacing=0,\n                truncate_label=-1,\n            )\n\n            # Create histogram data\n            data = variables[var_x]\n            hist, bin_edges = np.histogram(data, bins=12)\n            hist_data = [(float(bin_edges[k]), float(bin_edges[k + 1]), float(hist[k])) for k in range(len(hist))]\n            chart.add(var_x, hist_data)\n        else:\n            # Off-diagonal: Scatter plot with smaller dots and better transparency\n            chart = pygal.XY(\n                width=inner_size,\n                height=inner_size,\n                style=custom_style,\n                show_legend=False,\n                show_x_labels=(i == n_vars - 1),\n                show_y_labels=(j == 0),\n                x_label_rotation=0,\n                show_minor_x_labels=False,\n                show_minor_y_labels=False,\n                margin_top=8,\n                margin_right=8,\n                margin_bottom=40 if i == n_vars - 1 else 8,\n                margin_left=70 if j == 0 else 8,\n                dots_size=7,\n                stroke=False,\n                truncate_label=-1,\n            )\n\n            # Scatter data as (x, y) tuples\n            x_data = variables[var_x]\n            y_data = variables[var_y]\n            scatter_data = [(float(x_data[k]), float(y_data[k])) for k in range(len(x_data))]\n            chart.add(\"Data\", scatter_data)\n\n        # Render chart to PNG bytes\n        svg_bytes = chart.render()\n        png_bytes = cairosvg.svg2png(bytestring=svg_bytes, output_width=inner_size, output_height=inner_size)\n        cell_image = Image.open(BytesIO(png_bytes))\n\n        # Paste into composite\n        composite.paste(cell_image, (x_pos, y_pos))\n\n# Add title and labels using PIL\ndraw = ImageDraw.Draw(composite)\n\n# Try to use a nice font, fall back to default\ntry:\n    title_font = ImageFont.truetype(\"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf\", 56)\n    label_font = ImageFont.truetype(\"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf\", 36)\nexcept OSError:\n    title_font = ImageFont.load_default()\n    label_font = ImageFont.load_default()\n\n# Title\ntitle_text = \"scatter-matrix · pygal · pyplots.ai\"\ntitle_bbox = draw.textbbox((0, 0), title_text, font=title_font)\ntitle_width = title_bbox[2] - title_bbox[0]\ndraw.text(((total_width - title_width) // 2, 35), title_text, fill=\"#333\", font=title_font)\n\n# Variable labels along bottom and left\nfor idx, var_name in enumerate(var_names):\n    # Bottom labels\n    x_label_pos = margin_left + idx * cell_size + cell_size // 2\n    y_label_pos = margin_top + n_vars * cell_size + 40\n    bbox = draw.textbbox((0, 0), var_name, font=label_font)\n    text_width = bbox[2] - bbox[0]\n    draw.text((x_label_pos - text_width // 2, y_label_pos), var_name, fill=\"#333\", font=label_font)\n\n    # Left labels (rotated - draw text vertically, positioned closer to plots)\n    x_label_pos = 15\n    y_label_pos = margin_top + idx * cell_size + cell_size // 2\n\n    # Create rotated text image\n    txt_img = Image.new(\"RGBA\", (350, 60), (255, 255, 255, 0))\n    txt_draw = ImageDraw.Draw(txt_img)\n    txt_draw.text((0, 0), var_name, fill=\"#333\", font=label_font)\n    txt_rotated = txt_img.rotate(90, expand=True)\n\n    # Paste rotated text\n    bbox = draw.textbbox((0, 0), var_name, font=label_font)\n    text_height = bbox[2] - bbox[0]\n    paste_y = y_label_pos - text_height // 2\n    composite.paste(txt_rotated, (x_label_pos, paste_y), txt_rotated)\n\n# Save output\ncomposite.save(\"plot.png\", \"PNG\", dpi=(300, 300))\n"}