|
| 1 | +""" pyplots.ai |
| 2 | +venn-basic: Venn Diagram |
| 3 | +Library: plotnine 0.15.2 | Python 3.13.11 |
| 4 | +Quality: 91/100 | Created: 2025-12-29 |
| 5 | +""" |
| 6 | + |
| 7 | +import math |
| 8 | + |
| 9 | +import numpy as np |
| 10 | +import pandas as pd |
| 11 | +from plotnine import ( |
| 12 | + aes, |
| 13 | + element_blank, |
| 14 | + element_rect, |
| 15 | + element_text, |
| 16 | + geom_polygon, |
| 17 | + geom_text, |
| 18 | + ggplot, |
| 19 | + labs, |
| 20 | + scale_fill_manual, |
| 21 | + scale_x_continuous, |
| 22 | + scale_y_continuous, |
| 23 | + theme, |
| 24 | +) |
| 25 | + |
| 26 | + |
| 27 | +# Data - Three overlapping sets representing skills in a tech team |
| 28 | +# Set A: Python developers (100 people) |
| 29 | +# Set B: Data Scientists (80 people) |
| 30 | +# Set C: ML Engineers (60 people) |
| 31 | +# Overlaps: A∩B=30, A∩C=20, B∩C=25, A∩B∩C=10 |
| 32 | + |
| 33 | +set_labels = ["Python\nDevelopers", "Data\nScientists", "ML\nEngineers"] |
| 34 | +# Region counts (exclusive to each region) |
| 35 | +# A only: 100 - 30 - 20 + 10 = 60 |
| 36 | +# B only: 80 - 30 - 25 + 10 = 35 |
| 37 | +# C only: 60 - 20 - 25 + 10 = 25 |
| 38 | +# A∩B only: 30 - 10 = 20 |
| 39 | +# A∩C only: 20 - 10 = 10 |
| 40 | +# B∩C only: 25 - 10 = 15 |
| 41 | +# A∩B∩C: 10 |
| 42 | + |
| 43 | +region_counts = {"A_only": 60, "B_only": 35, "C_only": 25, "AB_only": 20, "AC_only": 10, "BC_only": 15, "ABC": 10} |
| 44 | + |
| 45 | + |
| 46 | +# Circle positions for 3-set Venn diagram |
| 47 | +# Circles arranged in a triangle formation with significant overlap |
| 48 | +radius = 5 |
| 49 | +offset = radius * 0.6 # Controls overlap amount |
| 50 | +n_points = 100 |
| 51 | + |
| 52 | +# Circle centers |
| 53 | +centers = { |
| 54 | + "A": (-offset, offset * 0.5), # Top-left |
| 55 | + "B": (offset, offset * 0.5), # Top-right |
| 56 | + "C": (0, -offset * 0.9), # Bottom |
| 57 | +} |
| 58 | + |
| 59 | +# Colors with transparency for overlap visibility |
| 60 | +colors = { |
| 61 | + "A": "#306998", # Python Blue |
| 62 | + "B": "#FFD43B", # Python Yellow |
| 63 | + "C": "#4B8BBE", # Lighter blue |
| 64 | +} |
| 65 | + |
| 66 | +# Create circle polygons |
| 67 | +circle_rows = [] |
| 68 | +circle_id = 0 |
| 69 | +for label, (cx, cy) in centers.items(): |
| 70 | + angles = np.linspace(0, 2 * math.pi, n_points + 1) |
| 71 | + x_coords = cx + radius * np.cos(angles) |
| 72 | + y_coords = cy + radius * np.sin(angles) |
| 73 | + for i in range(len(x_coords)): |
| 74 | + circle_rows.append({"x": x_coords[i], "y": y_coords[i], "circle_id": circle_id, "set": label}) |
| 75 | + circle_id += 1 |
| 76 | + |
| 77 | +circle_df = pd.DataFrame(circle_rows) |
| 78 | + |
| 79 | +# Calculate positions for region labels |
| 80 | +# A only - left side of circle A |
| 81 | +label_A_only = { |
| 82 | + "x": centers["A"][0] - radius * 0.45, |
| 83 | + "y": centers["A"][1] + radius * 0.2, |
| 84 | + "label": str(region_counts["A_only"]), |
| 85 | +} |
| 86 | + |
| 87 | +# B only - right side of circle B |
| 88 | +label_B_only = { |
| 89 | + "x": centers["B"][0] + radius * 0.45, |
| 90 | + "y": centers["B"][1] + radius * 0.2, |
| 91 | + "label": str(region_counts["B_only"]), |
| 92 | +} |
| 93 | + |
| 94 | +# C only - bottom of circle C |
| 95 | +label_C_only = {"x": centers["C"][0], "y": centers["C"][1] - radius * 0.5, "label": str(region_counts["C_only"])} |
| 96 | + |
| 97 | +# AB intersection (top center) |
| 98 | +label_AB = {"x": 0, "y": centers["A"][1] + radius * 0.35, "label": str(region_counts["AB_only"])} |
| 99 | + |
| 100 | +# AC intersection (bottom-left) |
| 101 | +label_AC = { |
| 102 | + "x": centers["A"][0] + radius * 0.35, |
| 103 | + "y": (centers["A"][1] + centers["C"][1]) / 2 - radius * 0.1, |
| 104 | + "label": str(region_counts["AC_only"]), |
| 105 | +} |
| 106 | + |
| 107 | +# BC intersection (bottom-right) |
| 108 | +label_BC = { |
| 109 | + "x": centers["B"][0] - radius * 0.35, |
| 110 | + "y": (centers["B"][1] + centers["C"][1]) / 2 - radius * 0.1, |
| 111 | + "label": str(region_counts["BC_only"]), |
| 112 | +} |
| 113 | + |
| 114 | +# ABC intersection (center) |
| 115 | +centroid_x = (centers["A"][0] + centers["B"][0] + centers["C"][0]) / 3 |
| 116 | +centroid_y = (centers["A"][1] + centers["B"][1] + centers["C"][1]) / 3 |
| 117 | +label_ABC = {"x": centroid_x, "y": centroid_y, "label": str(region_counts["ABC"])} |
| 118 | + |
| 119 | +# Create label dataframes |
| 120 | +count_labels_df = pd.DataFrame([label_A_only, label_B_only, label_C_only, label_AB, label_AC, label_BC, label_ABC]) |
| 121 | + |
| 122 | +# Set name labels - positioned outside circles |
| 123 | +set_name_labels = [ |
| 124 | + {"x": centers["A"][0] - radius * 0.8, "y": centers["A"][1] + radius * 0.9, "label": set_labels[0]}, |
| 125 | + {"x": centers["B"][0] + radius * 0.8, "y": centers["B"][1] + radius * 0.9, "label": set_labels[1]}, |
| 126 | + {"x": centers["C"][0], "y": centers["C"][1] - radius * 1.1, "label": set_labels[2]}, |
| 127 | +] |
| 128 | +set_name_df = pd.DataFrame(set_name_labels) |
| 129 | + |
| 130 | +# Plot |
| 131 | +plot = ( |
| 132 | + ggplot() |
| 133 | + # Draw circles with transparency for overlap visibility |
| 134 | + + geom_polygon( |
| 135 | + aes(x="x", y="y", fill="set", group="circle_id"), data=circle_df, alpha=0.45, color="#333333", size=1.5 |
| 136 | + ) |
| 137 | + # Region count labels (larger, bold) |
| 138 | + + geom_text(aes(x="x", y="y", label="label"), data=count_labels_df, size=18, fontweight="bold", color="#000000") |
| 139 | + # Set name labels (outside circles) |
| 140 | + + geom_text(aes(x="x", y="y", label="label"), data=set_name_df, size=14, fontweight="bold", color="#333333") |
| 141 | + # Colors (legend hidden via theme) |
| 142 | + + scale_fill_manual(values=colors) |
| 143 | + # Axis scaling for proper aspect ratio |
| 144 | + + scale_x_continuous(limits=(-12, 12)) |
| 145 | + + scale_y_continuous(limits=(-12, 10)) |
| 146 | + # Title |
| 147 | + + labs(title="venn-basic · plotnine · pyplots.ai") |
| 148 | + # Clean theme |
| 149 | + + theme( |
| 150 | + figure_size=(12, 12), |
| 151 | + plot_title=element_text(size=24, ha="center", color="#333333"), |
| 152 | + axis_title=element_blank(), |
| 153 | + axis_text=element_blank(), |
| 154 | + axis_ticks=element_blank(), |
| 155 | + axis_line=element_blank(), |
| 156 | + panel_grid_major=element_blank(), |
| 157 | + panel_grid_minor=element_blank(), |
| 158 | + panel_background=element_rect(fill="#FFFFFF"), |
| 159 | + plot_background=element_rect(fill="#FFFFFF"), |
| 160 | + legend_position="none", # Hide legend - labels are on the plot |
| 161 | + ) |
| 162 | +) |
| 163 | + |
| 164 | +# Save |
| 165 | +plot.save("plot.png", dpi=300) |
0 commit comments