{"spec_id":"subplot-mosaic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nsubplot-mosaic: Mosaic Subplot Layout with Varying Sizes\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-05-14\n\"\"\"\n\nimport os\nimport sys\nfrom io import BytesIO\n\n\n# Avoid shadowing the pygal package by this script\nsys.dont_write_bytecode = True\n_this_file = os.path.abspath(__file__)\n_this_dir = os.path.dirname(_this_file)\nif _this_dir in sys.path:\n    sys.path.remove(_this_dir)\n\nimport cairosvg\nimport numpy as np\nimport pygal\nfrom PIL import Image, ImageDraw, ImageFont\nfrom pygal.style import Style\n\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\nIMPRINT = (\n    \"#009E73\",  # brand green (first series)\n    \"#C475FD\",  # vermillion\n    \"#4467A3\",  # blue\n    \"#BD8233\",  # reddish purple\n    \"#AE3030\",  # orange\n    \"#2ABCCD\",  # sky blue\n    \"#954477\",  # yellow\n)\n\n# Data - Dashboard showing sales performance across different dimensions\nnp.random.seed(42)\n\n# Time series data for main overview chart (Panel A - wide)\nmonths = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"]\nrevenue = [120, 135, 142, 138, 155, 168, 172, 185, 178, 192, 205, 218]\ncosts = [85, 88, 92, 90, 98, 105, 108, 115, 112, 120, 128, 135]\n\n# Category data for bar chart (Panel B)\ncategories = [\"Electronics\", \"Clothing\", \"Home\", \"Sports\", \"Books\"]\ncategory_sales = [450, 320, 280, 195, 165]\n\n# Pie chart data for market share (Panel C)\nregions = [\"North\", \"South\", \"East\", \"West\"]\nregion_shares = [35, 28, 22, 15]\n\n# Scatter data for correlation (Panel D)\nn_points = 40\nmarketing_spend = np.random.uniform(10, 100, n_points)\nsales_response = marketing_spend * 2.5 + np.random.normal(0, 25, n_points)\n\n# Gauge data for KPI (Panel E)\ncurrent_target_pct = 78\n\n# Custom style for theme\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    colors=IMPRINT,\n    font_family=\"sans-serif\",\n    title_font_size=28,\n    label_font_size=22,\n    major_label_font_size=18,\n    legend_font_size=16,\n    value_font_size=14,\n    stroke_width=3,\n)\n\n# Mosaic layout pattern: \"AAB;AAC;DDF\"\n# A = large chart (2x2), B = medium chart (1x1), C = medium chart (1x1)\n# D = medium chart (2x1), F = placeholder (empty cell)\n\ntotal_width = 4800\ntotal_height = 2700\ntitle_height = 120\npadding = 20\n\n# Calculate cell sizes for 3-column, 3-row grid\ngrid_width = total_width - 2 * padding\ngrid_height = total_height - title_height - 2 * padding\ncol_width = grid_width // 3\nrow_height = grid_height // 3\n\n# Panel A: Line chart (spans 2 cols, 2 rows) - Revenue & Costs over time\nchart_a = pygal.Line(\n    width=int(col_width * 2),\n    height=int(row_height * 2),\n    style=custom_style,\n    show_legend=True,\n    legend_at_bottom=True,\n    show_y_guides=True,\n    show_x_guides=False,\n    x_title=\"Month\",\n    y_title=\"Amount ($K)\",\n    title=\"Monthly Revenue vs Costs\",\n    show_dots=True,\n    dots_size=8,\n    stroke_style={\"width\": 3},\n    truncate_label=-1,\n)\nchart_a.x_labels = months\nchart_a.add(\"Revenue\", revenue)\nchart_a.add(\"Costs\", costs)\n\n# Panel B: Horizontal bar chart (1 col, 1 row) - Category sales\nchart_b = pygal.HorizontalBar(\n    width=int(col_width),\n    height=int(row_height),\n    style=custom_style,\n    show_legend=False,\n    show_y_guides=True,\n    title=\"Sales by Category\",\n    truncate_label=-1,\n    print_values=True,\n    print_values_position=\"center\",\n    value_font_size=14,\n)\nfor cat, val in zip(categories, category_sales, strict=True):\n    chart_b.add(cat, val)\n\n# Panel C: Pie chart (1 col, 1 row) - Regional distribution\nchart_c = pygal.Pie(\n    width=int(col_width),\n    height=int(row_height),\n    style=custom_style,\n    show_legend=True,\n    legend_at_bottom=True,\n    title=\"Regional Share\",\n    inner_radius=0.4,\n    truncate_label=-1,\n)\nfor region, share in zip(regions, region_shares, strict=True):\n    chart_c.add(region, share)\n\n# Panel D: XY scatter chart (2 cols, 1 row) - Marketing vs Sales\nchart_d = pygal.XY(\n    width=int(col_width * 2),\n    height=int(row_height),\n    style=custom_style,\n    show_legend=False,\n    show_y_guides=True,\n    x_title=\"Marketing Spend ($K)\",\n    y_title=\"Sales ($K)\",\n    title=\"Marketing ROI Correlation\",\n    stroke=False,\n    dots_size=10,\n    truncate_label=-1,\n)\nscatter_data = [(float(x), float(y)) for x, y in zip(marketing_spend, sales_response, strict=True)]\nchart_d.add(\"Correlation\", scatter_data)\n\n# Panel E: Gauge chart (1 col, 1 row) - Target achievement\nchart_e = pygal.SolidGauge(\n    width=int(col_width),\n    height=int(row_height),\n    style=custom_style,\n    show_legend=False,\n    title=\"Target Achievement\",\n    inner_radius=0.6,\n    half_pie=True,\n)\nchart_e.add(\"Progress\", [{\"value\": current_target_pct, \"max_value\": 100}])\n\n# Render charts to PNG via SVG+Cairo\nsvg_a = chart_a.render()\npng_a = cairosvg.svg2png(bytestring=svg_a, output_width=int(col_width * 2), output_height=int(row_height * 2))\nimg_a = Image.open(BytesIO(png_a))\n\nsvg_b = chart_b.render()\npng_b = cairosvg.svg2png(bytestring=svg_b, output_width=int(col_width), output_height=int(row_height))\nimg_b = Image.open(BytesIO(png_b))\n\nsvg_c = chart_c.render()\npng_c = cairosvg.svg2png(bytestring=svg_c, output_width=int(col_width), output_height=int(row_height))\nimg_c = Image.open(BytesIO(png_c))\n\nsvg_d = chart_d.render()\npng_d = cairosvg.svg2png(bytestring=svg_d, output_width=int(col_width * 2), output_height=int(row_height))\nimg_d = Image.open(BytesIO(png_d))\n\nsvg_e = chart_e.render()\npng_e = cairosvg.svg2png(bytestring=svg_e, output_width=int(col_width), output_height=int(row_height))\nimg_e = Image.open(BytesIO(png_e))\n\n# Create placeholder image (empty cell) with theme-adaptive background\nplaceholder = Image.new(\"RGB\", (int(col_width), int(row_height)), PAGE_BG)\n\n# Create combined image\ncombined = Image.new(\"RGB\", (total_width, total_height), PAGE_BG)\n\n# Place charts according to mosaic pattern: \"AAB;AAC;DDF\"\n# Row 0: A (cols 0-1), B (col 2)\n# Row 1: A (cols 0-1), C (col 2)\n# Row 2: D (cols 0-1), F (col 2, empty)\n\nx_offset = padding\ny_offset = title_height + padding\n\ncombined.paste(img_a, (x_offset, y_offset))\ncombined.paste(img_b, (x_offset + int(col_width * 2), y_offset))\ncombined.paste(img_c, (x_offset + int(col_width * 2), y_offset + int(row_height)))\ncombined.paste(img_d, (x_offset, y_offset + int(row_height * 2)))\ncombined.paste(placeholder, (x_offset + int(col_width * 2), y_offset + int(row_height * 2)))\n\n# Add main title\ndraw = ImageDraw.Draw(combined)\n\ntry:\n    title_font = ImageFont.truetype(\"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf\", 56)\nexcept OSError:\n    title_font = ImageFont.load_default()\n\ntitle_text = \"subplot-mosaic · pygal · anyplot.ai\"\nbbox = draw.textbbox((0, 0), title_text, font=title_font)\ntitle_width = bbox[2] - bbox[0]\ntitle_x = (total_width - title_width) // 2\ndraw.text((title_x, 30), title_text, fill=INK, font=title_font)\n\n# Save final image\ncombined.save(f\"plot-{THEME}.png\", dpi=(300, 300))\n\n# Save as HTML with interactive SVG grid\nhtml_content = f\"\"\"<!DOCTYPE html>\n<html>\n<head>\n    <title>subplot-mosaic · pygal · anyplot.ai</title>\n    <style>\n        body {{ font-family: sans-serif; background: {PAGE_BG}; margin: 20px; }}\n        h1 {{ text-align: center; color: {INK}; font-size: 32px; margin-bottom: 20px; }}\n        .mosaic {{\n            display: grid;\n            grid-template-columns: 1fr 1fr 1fr;\n            grid-template-rows: 1fr 1fr 1fr;\n            gap: 10px;\n            max-width: 1600px;\n            margin: 0 auto;\n            height: 900px;\n        }}\n        .panel-a {{ grid-column: 1 / 3; grid-row: 1 / 3; }}\n        .panel-b {{ grid-column: 3; grid-row: 1; }}\n        .panel-c {{ grid-column: 3; grid-row: 2; }}\n        .panel-d {{ grid-column: 1 / 3; grid-row: 3; }}\n        .panel-f {{ grid-column: 3; grid-row: 3; background: {PAGE_BG}; }}\n        .panel svg {{ width: 100%; height: 100%; }}\n    </style>\n</head>\n<body>\n    <h1>subplot-mosaic · pygal · anyplot.ai</h1>\n    <div class=\"mosaic\">\n\"\"\"\n\nsvg_a_str = chart_a.render(is_unicode=True).replace('<?xml version=\"1.0\" encoding=\"utf-8\"?>', \"\")\nsvg_b_str = chart_b.render(is_unicode=True).replace('<?xml version=\"1.0\" encoding=\"utf-8\"?>', \"\")\nsvg_c_str = chart_c.render(is_unicode=True).replace('<?xml version=\"1.0\" encoding=\"utf-8\"?>', \"\")\nsvg_d_str = chart_d.render(is_unicode=True).replace('<?xml version=\"1.0\" encoding=\"utf-8\"?>', \"\")\nsvg_e_str = chart_e.render(is_unicode=True).replace('<?xml version=\"1.0\" encoding=\"utf-8\"?>', \"\")\n\nhtml_content += f'        <div class=\"panel panel-a\">{svg_a_str}</div>\\n'\nhtml_content += f'        <div class=\"panel panel-b\">{svg_b_str}</div>\\n'\nhtml_content += f'        <div class=\"panel panel-c\">{svg_c_str}</div>\\n'\nhtml_content += f'        <div class=\"panel panel-d\">{svg_d_str}</div>\\n'\nhtml_content += '        <div class=\"panel panel-f\"></div>\\n'\nhtml_content += \"\"\"    </div>\n</body>\n</html>\"\"\"\n\nwith open(f\"plot-{THEME}.html\", \"w\") as f:\n    f.write(html_content)\n"}