|
1 | 1 | """ pyplots.ai |
2 | 2 | qrcode-basic: Basic QR Code Generator |
3 | | -Library: altair 6.0.0 | Python 3.13.11 |
4 | | -Quality: 91/100 | Created: 2026-01-07 |
| 3 | +Library: altair 6.0.0 | Python 3.14.3 |
| 4 | +Quality: 91/100 | Updated: 2026-04-07 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import altair as alt |
8 | 8 | import numpy as np |
9 | 9 | import pandas as pd |
| 10 | +import qrcode |
10 | 11 |
|
11 | 12 |
|
12 | | -# Data - Generate QR code pattern (Version 1, 21x21 modules) |
13 | | -# Creates a proper QR code structure with finder patterns |
14 | | -np.random.seed(42) |
15 | | - |
16 | | -size = 21 # Version 1 QR code size |
17 | | -quiet_zone = 4 # Standard quiet zone (white border) |
18 | | -total_size = size + 2 * quiet_zone |
19 | | - |
20 | | -# Initialize matrix with white (0) |
21 | | -matrix = np.zeros((total_size, total_size), dtype=int) |
22 | | - |
23 | | -# Add finder pattern at top-left |
24 | | -for i in range(7): |
25 | | - matrix[quiet_zone + i, quiet_zone] = 1 |
26 | | - matrix[quiet_zone + i, quiet_zone + 6] = 1 |
27 | | - matrix[quiet_zone, quiet_zone + i] = 1 |
28 | | - matrix[quiet_zone + 6, quiet_zone + i] = 1 |
29 | | -for i in range(2, 5): |
30 | | - for j in range(2, 5): |
31 | | - matrix[quiet_zone + i, quiet_zone + j] = 1 |
32 | | - |
33 | | -# Add finder pattern at top-right |
34 | | -tr_col = quiet_zone + size - 7 |
35 | | -for i in range(7): |
36 | | - matrix[quiet_zone + i, tr_col] = 1 |
37 | | - matrix[quiet_zone + i, tr_col + 6] = 1 |
38 | | - matrix[quiet_zone, tr_col + i] = 1 |
39 | | - matrix[quiet_zone + 6, tr_col + i] = 1 |
40 | | -for i in range(2, 5): |
41 | | - for j in range(2, 5): |
42 | | - matrix[quiet_zone + i, tr_col + j] = 1 |
43 | | - |
44 | | -# Add finder pattern at bottom-left |
45 | | -bl_row = quiet_zone + size - 7 |
46 | | -for i in range(7): |
47 | | - matrix[bl_row + i, quiet_zone] = 1 |
48 | | - matrix[bl_row + i, quiet_zone + 6] = 1 |
49 | | - matrix[bl_row, quiet_zone + i] = 1 |
50 | | - matrix[bl_row + 6, quiet_zone + i] = 1 |
51 | | -for i in range(2, 5): |
52 | | - for j in range(2, 5): |
53 | | - matrix[bl_row + i, quiet_zone + j] = 1 |
54 | | - |
55 | | -# Add timing patterns (alternating black/white lines between finder patterns) |
56 | | -for i in range(8, size - 8): |
57 | | - matrix[quiet_zone + 6, quiet_zone + i] = (i + 1) % 2 |
58 | | - matrix[quiet_zone + i, quiet_zone + 6] = (i + 1) % 2 |
59 | | - |
60 | | -# Add dark module (always black, required in QR codes) |
61 | | -matrix[quiet_zone + size - 8, quiet_zone + 8] = 1 |
62 | | - |
63 | | -# Fill data area with deterministic pattern representing encoded data |
| 13 | +# Data - Generate a real, scannable QR code encoding "https://pyplots.ai" |
64 | 14 | content = "https://pyplots.ai" |
65 | | -hash_val = hash(content) |
66 | | -for row in range(total_size): |
67 | | - for col in range(total_size): |
68 | | - qr_row = row - quiet_zone |
69 | | - qr_col = col - quiet_zone |
70 | | - |
71 | | - # Skip quiet zone |
72 | | - if row < quiet_zone or row >= total_size - quiet_zone: |
73 | | - continue |
74 | | - if col < quiet_zone or col >= total_size - quiet_zone: |
75 | | - continue |
76 | | - |
77 | | - # Skip finder patterns and separators |
78 | | - if qr_row < 9 and qr_col < 9: |
79 | | - continue |
80 | | - if qr_row < 9 and qr_col >= size - 8: |
81 | | - continue |
82 | | - if qr_row >= size - 8 and qr_col < 9: |
83 | | - continue |
84 | | - |
85 | | - # Skip timing patterns |
86 | | - if qr_row == 6 or qr_col == 6: |
87 | | - continue |
88 | 15 |
|
89 | | - # Create deterministic pattern from position and content hash |
90 | | - idx = row * total_size + col |
91 | | - matrix[row, col] = ((hash_val >> (idx % 32)) ^ (idx * 7)) % 2 |
92 | | - |
93 | | -# Convert matrix to DataFrame for Altair |
94 | | -data = [] |
95 | | -for row in range(total_size): |
96 | | - for col in range(total_size): |
97 | | - data.append({"x": col, "y": total_size - 1 - row, "value": matrix[row, col]}) |
98 | | -df = pd.DataFrame(data) |
99 | | - |
100 | | -# Create QR code visualization using mark_rect |
| 16 | +qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_M, box_size=1, border=0) |
| 17 | +qr.add_data(content) |
| 18 | +qr.make(fit=True) |
| 19 | + |
| 20 | +# Convert QR code to numpy matrix and add quiet zone |
| 21 | +qr_matrix = np.array(qr.get_matrix(), dtype=int) |
| 22 | +quiet_zone = 4 |
| 23 | +padded = np.zeros((qr_matrix.shape[0] + 2 * quiet_zone, qr_matrix.shape[1] + 2 * quiet_zone), dtype=int) |
| 24 | +padded[quiet_zone : quiet_zone + qr_matrix.shape[0], quiet_zone : quiet_zone + qr_matrix.shape[1]] = qr_matrix |
| 25 | +total_size = padded.shape[0] |
| 26 | + |
| 27 | +# Build DataFrame with region classification and color encoding |
| 28 | +rows, cols = np.where(np.ones_like(padded, dtype=bool)) |
| 29 | +region_colors = {"Finder Pattern": "#1B3A5C", "Timing Pattern": "#2A7F62", "Data": "#306998", "Quiet Zone": "#FFFFFF"} |
| 30 | +records = [] |
| 31 | +for r, c in zip(rows, cols, strict=True): |
| 32 | + qr_r, qr_c = r - quiet_zone, c - quiet_zone |
| 33 | + if qr_r < 0 or qr_r >= qr_matrix.shape[0] or qr_c < 0 or qr_c >= qr_matrix.shape[1]: |
| 34 | + region = "Quiet Zone" |
| 35 | + elif ( |
| 36 | + (qr_r < 7 and qr_c < 7) |
| 37 | + or (qr_r < 7 and qr_c >= qr_matrix.shape[1] - 7) |
| 38 | + or (qr_r >= qr_matrix.shape[0] - 7 and qr_c < 7) |
| 39 | + ): |
| 40 | + region = "Finder Pattern" |
| 41 | + elif qr_r == 6 or qr_c == 6: |
| 42 | + region = "Timing Pattern" |
| 43 | + else: |
| 44 | + region = "Data" |
| 45 | + val = padded[r, c] |
| 46 | + display_region = region if val == 1 else "Background" |
| 47 | + records.append({"x": c, "y": total_size - 1 - r, "value": val, "region": region, "display": display_region}) |
| 48 | + |
| 49 | +data = pd.DataFrame(records) |
| 50 | + |
| 51 | +# Color mapping: dark modules get region-specific colors, background is white |
| 52 | +display_domain = ["Finder Pattern", "Timing Pattern", "Data", "Background"] |
| 53 | +display_colors = ["#1B3A5C", "#2A7F62", "#306998", "#FFFFFF"] |
| 54 | + |
| 55 | +# Plot - QR code with color-encoded structural regions |
101 | 56 | chart = ( |
102 | | - alt.Chart(df) |
103 | | - .mark_rect(stroke=None) |
| 57 | + alt.Chart(data) |
| 58 | + .mark_rect(stroke=None, strokeWidth=0) |
104 | 59 | .encode( |
105 | 60 | x=alt.X("x:O", axis=None), |
106 | 61 | y=alt.Y("y:O", axis=None), |
107 | | - color=alt.Color("value:N", scale=alt.Scale(domain=[0, 1], range=["#FFFFFF", "#000000"]), legend=None), |
| 62 | + color=alt.Color( |
| 63 | + "display:N", |
| 64 | + scale=alt.Scale(domain=display_domain, range=display_colors), |
| 65 | + legend=alt.Legend( |
| 66 | + title="QR Code Regions", |
| 67 | + titleFontSize=16, |
| 68 | + labelFontSize=14, |
| 69 | + orient="bottom", |
| 70 | + direction="horizontal", |
| 71 | + values=["Finder Pattern", "Timing Pattern", "Data"], |
| 72 | + ), |
| 73 | + ), |
| 74 | + tooltip=[ |
| 75 | + alt.Tooltip("region:N", title="Region"), |
| 76 | + alt.Tooltip("x:O", title="Col"), |
| 77 | + alt.Tooltip("y:O", title="Row"), |
| 78 | + ], |
108 | 79 | ) |
109 | 80 | .properties( |
110 | 81 | width=800, |
111 | 82 | height=800, |
112 | | - title=alt.Title("qrcode-basic · altair · pyplots.ai", fontSize=28, anchor="middle", dy=-10), |
| 83 | + title=alt.Title( |
| 84 | + "qrcode-basic · altair · pyplots.ai", |
| 85 | + subtitle="Structural regions: Finder Patterns (navy) for orientation · Timing Patterns (green) for alignment · Data modules (blue)", |
| 86 | + fontSize=28, |
| 87 | + subtitleFontSize=16, |
| 88 | + subtitleColor="#555555", |
| 89 | + anchor="middle", |
| 90 | + dy=-10, |
| 91 | + ), |
113 | 92 | ) |
114 | 93 | .configure_view(strokeWidth=0) |
115 | 94 | .configure_axis(grid=False) |
| 95 | + .configure_legend(symbolStrokeWidth=0, padding=20) |
116 | 96 | ) |
117 | 97 |
|
118 | | -# Save as PNG (square format: 3600 x 3600 px with scale_factor) |
| 98 | +# Save |
119 | 99 | chart.save("plot.png", scale_factor=4.5) |
120 | | - |
121 | | -# Save interactive HTML version |
122 | 100 | chart.save("plot.html") |
0 commit comments