|
1 | | -""" pyplots.ai |
| 1 | +"""pyplots.ai |
2 | 2 | arc-basic: Basic Arc Diagram |
3 | | -Library: letsplot 4.8.2 | Python 3.13.11 |
4 | | -Quality: 92/100 | Created: 2025-12-23 |
| 3 | +Library: letsplot 4.8.2 | Python 3.14.3 |
| 4 | +Quality: /100 | Updated: 2026-02-23 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import numpy as np |
|
10 | 10 | LetsPlot, |
11 | 11 | aes, |
12 | 12 | element_blank, |
| 13 | + element_rect, |
13 | 14 | element_text, |
14 | 15 | geom_path, |
15 | 16 | geom_point, |
| 17 | + geom_segment, |
16 | 18 | geom_text, |
17 | 19 | ggplot, |
18 | 20 | ggsize, |
19 | 21 | labs, |
| 22 | + layer_tooltips, |
| 23 | + scale_alpha_identity, |
| 24 | + scale_color_identity, |
20 | 25 | scale_size_identity, |
21 | 26 | theme, |
22 | 27 | xlim, |
|
28 | 33 | LetsPlot.setup_html() |
29 | 34 |
|
30 | 35 | # Data: Character interactions in a story chapter |
31 | | -np.random.seed(42) |
32 | | - |
33 | 36 | nodes = ["Alice", "Bob", "Carol", "David", "Eve", "Frank", "Grace", "Henry", "Iris", "Jack"] |
34 | 37 | n_nodes = len(nodes) |
35 | 38 |
|
36 | 39 | # Edges: pairs of connected nodes with weights (source, target, weight) |
37 | 40 | edges = [ |
38 | | - (0, 1, 3), # Alice-Bob (strong connection) |
| 41 | + (0, 1, 3), # Alice-Bob (strong) |
39 | 42 | (0, 3, 2), # Alice-David |
40 | 43 | (1, 2, 2), # Bob-Carol |
41 | 44 | (2, 4, 1), # Carol-Eve |
|
54 | 57 |
|
55 | 58 | # Node positions along x-axis |
56 | 59 | x_positions = np.linspace(0, 1, n_nodes) |
57 | | -y_baseline = 0.1 |
| 60 | +y_baseline = 0.08 |
| 61 | + |
| 62 | +# Count connections per node for visual hierarchy |
| 63 | +connections = [0] * n_nodes |
| 64 | +for s, t, w in edges: |
| 65 | + connections[s] += w |
| 66 | + connections[t] += w |
| 67 | + |
| 68 | +# Arc color intensity by weight |
| 69 | +weight_colors = {1: "#93B8CC", 2: "#306998", 3: "#1A3A5C"} |
| 70 | +weight_alphas = {1: 0.5, 2: 0.65, 3: 0.8} |
| 71 | +weight_labels = {1: "Weak", 2: "Moderate", 3: "Strong"} |
58 | 72 |
|
59 | 73 | # Create arc data for geom_path |
60 | 74 | arc_data = [] |
|
66 | 80 | distance = abs(end - start) |
67 | 81 | height = 0.08 * distance |
68 | 82 |
|
69 | | - # Generate points along the arc (semi-circle) |
| 83 | + # Generate points along the arc |
70 | 84 | n_points = 50 |
71 | 85 | t = np.linspace(0, np.pi, n_points) |
72 | 86 | arc_x = x_start + (x_end - x_start) * (1 - np.cos(t)) / 2 |
73 | 87 | arc_y = y_baseline + height * np.sin(t) |
74 | 88 |
|
75 | | - # Line width based on weight |
76 | | - line_size = 1.5 + weight * 1.0 |
| 89 | + line_size = 1.0 + weight * 1.2 |
| 90 | + color = weight_colors[weight] |
| 91 | + alpha = weight_alphas[weight] |
| 92 | + tooltip_text = f"{nodes[start]} \u2194 {nodes[end]}" |
| 93 | + strength = weight_labels[weight] |
77 | 94 |
|
78 | 95 | for i in range(n_points): |
79 | | - arc_data.append({"x": arc_x[i], "y": arc_y[i], "edge_id": edge_id, "weight": weight, "size": line_size}) |
| 96 | + arc_data.append( |
| 97 | + { |
| 98 | + "x": arc_x[i], |
| 99 | + "y": arc_y[i], |
| 100 | + "edge_id": edge_id, |
| 101 | + "size": line_size, |
| 102 | + "color": color, |
| 103 | + "alpha": alpha, |
| 104 | + "connection": tooltip_text, |
| 105 | + "strength": strength, |
| 106 | + } |
| 107 | + ) |
80 | 108 |
|
81 | 109 | arc_df = pd.DataFrame(arc_data) |
82 | 110 |
|
83 | | -# Node data |
84 | | -node_df = pd.DataFrame({"x": x_positions, "y": [y_baseline] * n_nodes, "name": nodes}) |
| 111 | +# Node data with size based on total connection weight |
| 112 | +max_conn = max(connections) |
| 113 | +node_sizes = [6 + 10 * (c / max_conn) for c in connections] |
| 114 | +node_df = pd.DataFrame( |
| 115 | + {"x": x_positions, "y": [y_baseline] * n_nodes, "name": nodes, "node_size": node_sizes, "connections": connections} |
| 116 | +) |
| 117 | + |
| 118 | +# Baseline segment data |
| 119 | +baseline_df = pd.DataFrame({"x": [x_positions[0]], "xend": [x_positions[-1]], "y": [y_baseline], "yend": [y_baseline]}) |
85 | 120 |
|
86 | 121 | # Label data (positioned below nodes) |
87 | | -label_df = pd.DataFrame({"x": x_positions, "y": [y_baseline - 0.05] * n_nodes, "name": nodes}) |
| 122 | +label_df = pd.DataFrame({"x": x_positions, "y": [y_baseline - 0.045] * n_nodes, "name": nodes}) |
88 | 123 |
|
89 | | -# Create plot |
| 124 | +# Plot |
90 | 125 | plot = ( |
91 | 126 | ggplot() |
92 | | - # Draw arcs with semi-transparency for overlapping connections |
93 | | - + geom_path(data=arc_df, mapping=aes(x="x", y="y", group="edge_id", size="size"), color="#306998", alpha=0.6) |
| 127 | + # Subtle baseline |
| 128 | + + geom_segment(data=baseline_df, mapping=aes(x="x", y="y", xend="xend", yend="yend"), color="#D0D8E0", size=0.8) |
| 129 | + # Arcs with weight-based color, transparency, and tooltips |
| 130 | + + geom_path( |
| 131 | + data=arc_df, |
| 132 | + mapping=aes(x="x", y="y", group="edge_id", size="size", color="color", alpha="alpha"), |
| 133 | + tooltips=layer_tooltips().line("@connection").line("Strength: @strength"), |
| 134 | + ) |
| 135 | + + scale_size_identity() |
| 136 | + + scale_color_identity() |
| 137 | + + scale_alpha_identity() |
| 138 | + # Nodes sized by connection weight with tooltips |
| 139 | + + geom_point( |
| 140 | + data=node_df, |
| 141 | + mapping=aes(x="x", y="y", size="node_size"), |
| 142 | + color="#1A3A5C", |
| 143 | + fill="#FFD43B", |
| 144 | + stroke=1.5, |
| 145 | + shape=21, |
| 146 | + tooltips=layer_tooltips().line("@name").line("Connections: @connections"), |
| 147 | + ) |
94 | 148 | + scale_size_identity() |
95 | | - # Draw nodes |
96 | | - + geom_point(data=node_df, mapping=aes(x="x", y="y"), size=10, color="#FFD43B", fill="#FFD43B", stroke=2, shape=21) |
97 | | - # Add node labels |
| 149 | + # Node labels |
98 | 150 | + geom_text( |
99 | | - data=label_df, mapping=aes(x="x", y="y", label="name"), size=14, color="#306998", fontface="bold", vjust=1 |
| 151 | + data=label_df, mapping=aes(x="x", y="y", label="name"), size=16, color="#1A3A5C", fontface="bold", vjust=1 |
100 | 152 | ) |
101 | 153 | # Styling |
102 | | - + xlim(-0.05, 1.05) |
| 154 | + + xlim(-0.06, 1.06) |
103 | 155 | + ylim(-0.15, 0.85) |
104 | | - + labs(title="Character Interactions · arc-basic · letsplot · pyplots.ai") |
| 156 | + + labs(title="Character Interactions \u00b7 arc-basic \u00b7 letsplot \u00b7 pyplots.ai") |
105 | 157 | + theme( |
106 | 158 | axis_title=element_blank(), |
107 | 159 | axis_text=element_blank(), |
108 | 160 | axis_ticks=element_blank(), |
109 | 161 | axis_line=element_blank(), |
110 | 162 | panel_grid=element_blank(), |
111 | | - panel_background=element_blank(), |
112 | | - plot_title=element_text(size=24, face="bold"), |
| 163 | + panel_background=element_rect(fill="white", color="white"), |
| 164 | + plot_background=element_rect(fill="white", color="white"), |
| 165 | + plot_title=element_text(size=24, face="bold", color="#1A3A5C"), |
113 | 166 | legend_position="none", |
114 | 167 | ) |
115 | 168 | + ggsize(1600, 900) |
116 | 169 | ) |
117 | 170 |
|
118 | | -# Save as PNG (scale 3x to get 4800 x 2700 px) |
| 171 | +# Save |
119 | 172 | ggsave(plot, "plot.png", path=".", scale=3) |
120 | | - |
121 | | -# Save as HTML for interactive viewing |
122 | 173 | ggsave(plot, "plot.html", path=".") |
0 commit comments