|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | barcode-code128: Code 128 Barcode |
3 | | -Library: plotly 6.5.2 | Python 3.13.11 |
4 | | -Quality: 91/100 | Created: 2026-01-19 |
| 3 | +Library: plotly 6.7.0 | Python 3.13.13 |
| 4 | +Quality: 94/100 | Updated: 2026-05-21 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
| 8 | + |
7 | 9 | import plotly.graph_objects as go |
8 | 10 |
|
9 | 11 |
|
10 | | -# Code 128 encoding patterns (0 = space, 1 = bar) |
11 | | -# Each character is 11 modules (6 bars, quiet zone not included) |
| 12 | +# Theme tokens |
| 13 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 14 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 15 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 16 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 17 | + |
| 18 | +# Code 128 encoding patterns (11-module per symbol; STOP is 13 modules) |
12 | 19 | CODE128_PATTERNS = { |
13 | | - # Start codes |
14 | 20 | "START_A": "11010000100", |
15 | 21 | "START_B": "11010010000", |
16 | 22 | "START_C": "11010011100", |
17 | | - "STOP": "1100011101011", # Stop pattern (13 modules) |
18 | | - # Values 0-106 |
| 23 | + "STOP": "1100011101011", |
19 | 24 | 0: "11011001100", |
20 | 25 | 1: "11001101100", |
21 | 26 | 2: "11001100110", |
|
125 | 130 | 106: "1100011101011", |
126 | 131 | } |
127 | 132 |
|
128 | | -# Code 128B character to value mapping (ASCII 32-127) |
| 133 | +# Code 128B: ASCII 32-127 mapped to values 0-95 |
129 | 134 | CODE128B_MAP = {chr(i): i - 32 for i in range(32, 128)} |
130 | 135 |
|
131 | | -# Data - encode a sample string |
132 | | -content = "PYPLOTS-2024" |
| 136 | +# Data — shipping tracking code (Code 128B subset covers full alphanumeric range) |
| 137 | +content = "SHIP-2024-ABC123" |
133 | 138 |
|
134 | | -# Encode using Code 128B |
135 | | -values = [104] # Start B |
| 139 | +# Encode using Code 128B subset |
| 140 | +values = [104] # START_B |
136 | 141 | for char in content: |
137 | | - if char in CODE128B_MAP: |
138 | | - values.append(CODE128B_MAP[char]) |
139 | | - else: |
140 | | - values.append(0) # Space for unsupported chars |
| 142 | + values.append(CODE128B_MAP.get(char, 0)) |
141 | 143 |
|
142 | | -# Calculate check digit (modulo 103) |
| 144 | +# Check digit: modulo 103 algorithm (mandatory per spec) |
143 | 145 | checksum = values[0] |
144 | 146 | for i, val in enumerate(values[1:], 1): |
145 | 147 | checksum += i * val |
146 | 148 | checksum = checksum % 103 |
147 | 149 | values.append(checksum) |
148 | 150 |
|
149 | | -# Build pattern |
| 151 | +# Build binary pattern: START_B + data symbols + check digit + STOP |
150 | 152 | barcode_pattern = CODE128_PATTERNS["START_B"] |
151 | 153 | for val in values[1:-1]: |
152 | 154 | barcode_pattern += CODE128_PATTERNS[val] |
153 | | -barcode_pattern += CODE128_PATTERNS[values[-1]] # Check digit |
| 155 | +barcode_pattern += CODE128_PATTERNS[values[-1]] |
154 | 156 | barcode_pattern += CODE128_PATTERNS["STOP"] |
155 | 157 |
|
156 | | -# Calculate bar positions |
157 | | -bar_width = 3 |
| 158 | +# Calculate bar positions and widths from binary pattern |
| 159 | +QUIET_ZONE = 80 # generous quiet zone for reliable scanning |
| 160 | +MODULE_W = 4 # data units per module |
158 | 161 | x_positions = [] |
159 | | -widths = [] |
160 | | -current_x = 50 # Start with quiet zone |
| 162 | +bar_widths_list = [] |
| 163 | +current_x = QUIET_ZONE |
161 | 164 |
|
162 | 165 | for i, bit in enumerate(barcode_pattern): |
163 | 166 | if bit == "1": |
164 | | - # Find consecutive 1s |
165 | 167 | if i == 0 or barcode_pattern[i - 1] == "0": |
166 | | - start_x = current_x |
167 | | - width = bar_width |
| 168 | + width = MODULE_W |
168 | 169 | j = i + 1 |
169 | 170 | while j < len(barcode_pattern) and barcode_pattern[j] == "1": |
170 | | - width += bar_width |
| 171 | + width += MODULE_W |
171 | 172 | j += 1 |
172 | | - x_positions.append(start_x + width / 2) |
173 | | - widths.append(width) |
174 | | - current_x += bar_width |
| 173 | + x_positions.append(current_x + width / 2) |
| 174 | + bar_widths_list.append(width) |
| 175 | + current_x += MODULE_W |
| 176 | + |
| 177 | +total_width = QUIET_ZONE + len(barcode_pattern) * MODULE_W + QUIET_ZONE |
| 178 | + |
| 179 | +# Structural region boundaries in x coordinates (modules: START=11, each char=11, STOP=13) |
| 180 | +START_X0 = QUIET_ZONE |
| 181 | +START_X1 = QUIET_ZONE + 11 * MODULE_W |
| 182 | + |
| 183 | +data_start_x = START_X1 |
| 184 | +data_end_x = data_start_x + len(content) * 11 * MODULE_W |
| 185 | + |
| 186 | +check_x0 = data_end_x |
| 187 | +check_x1 = check_x0 + 11 * MODULE_W |
175 | 188 |
|
176 | | -# Calculate total width |
177 | | -total_width = 50 + len(barcode_pattern) * bar_width + 50 # quiet zones |
| 189 | +stop_x0 = check_x1 |
| 190 | +stop_x1 = stop_x0 + 13 * MODULE_W |
178 | 191 |
|
179 | 192 | # Plot |
180 | 193 | fig = go.Figure() |
181 | 194 |
|
182 | | -# Add bars |
183 | | -for x, w in zip(x_positions, widths, strict=True): |
184 | | - fig.add_shape(type="rect", x0=x - w / 2, x1=x + w / 2, y0=100, y1=500, fillcolor="black", line={"width": 0}) |
| 195 | +BAR_Y0 = 90 |
| 196 | +BAR_Y1 = 510 |
| 197 | +mid_bar_y = (BAR_Y0 + BAR_Y1) / 2 |
| 198 | + |
| 199 | +# Barcode bars — tall to utilize canvas space |
| 200 | +for x, w in zip(x_positions, bar_widths_list, strict=True): |
| 201 | + fig.add_shape(type="rect", x0=x - w / 2, x1=x + w / 2, y0=BAR_Y0, y1=BAR_Y1, fillcolor=INK, line={"width": 0}) |
| 202 | + |
| 203 | +# --- Structural anatomy annotations --- |
| 204 | +# Horizontal bracket line spanning the full barcode (above bars) |
| 205 | +BRACKET_Y = BAR_Y1 + 14 |
| 206 | +fig.add_shape( |
| 207 | + type="line", x0=START_X0, x1=stop_x1, y0=BRACKET_Y, y1=BRACKET_Y, line={"color": INK_SOFT, "width": 1}, opacity=0.55 |
| 208 | +) |
| 209 | + |
| 210 | +# Vertical tick marks at each region boundary |
| 211 | +for bx in [START_X0, START_X1, data_end_x, check_x1, stop_x1]: |
| 212 | + fig.add_shape( |
| 213 | + type="line", |
| 214 | + x0=bx, |
| 215 | + x1=bx, |
| 216 | + y0=BRACKET_Y - 6, |
| 217 | + y1=BRACKET_Y + 6, |
| 218 | + line={"color": INK_SOFT, "width": 1}, |
| 219 | + opacity=0.55, |
| 220 | + ) |
| 221 | + |
| 222 | +# Region labels just above the bracket line (narrow regions use smaller font) |
| 223 | +LABEL_Y = BRACKET_Y + 10 |
| 224 | +for x0, x1, label, fsize in [ |
| 225 | + (START_X0, START_X1, "START B", 10), |
| 226 | + (data_start_x, data_end_x, f"DATA ({len(content)} chars)", 11), |
| 227 | + (check_x0, check_x1, "CHK", 10), |
| 228 | + (stop_x0, stop_x1, "STOP", 10), |
| 229 | +]: |
| 230 | + fig.add_annotation( |
| 231 | + x=(x0 + x1) / 2, |
| 232 | + y=LABEL_Y, |
| 233 | + text=label, |
| 234 | + showarrow=False, |
| 235 | + font={"size": fsize, "color": INK_SOFT}, |
| 236 | + xanchor="center", |
| 237 | + yanchor="bottom", |
| 238 | + ) |
185 | 239 |
|
186 | | -# Add human-readable text below barcode |
| 240 | +# --- Hover interactivity (distinctive plotly feature) --- |
| 241 | +# Per-character data hover points — reveals encoded value for each character |
| 242 | +char_xs = [data_start_x + (i * 11 + 5.5) * MODULE_W for i in range(len(content))] |
| 243 | +char_labels = [ |
| 244 | + f"<b>'{char}'</b> · Code 128B value: {val}<br>Position {i + 1} / {len(content)}" |
| 245 | + for i, (char, val) in enumerate(zip(content, values[1:-1], strict=False)) |
| 246 | +] |
| 247 | +fig.add_trace( |
| 248 | + go.Scatter( |
| 249 | + x=char_xs, |
| 250 | + y=[mid_bar_y] * len(char_xs), |
| 251 | + mode="markers", |
| 252 | + marker={"opacity": 0, "size": 30, "color": INK}, |
| 253 | + text=char_labels, |
| 254 | + hovertemplate="%{text}<extra></extra>", |
| 255 | + showlegend=False, |
| 256 | + hoverlabel={"bgcolor": PAGE_BG, "bordercolor": INK_SOFT, "font": {"color": INK, "size": 13}}, |
| 257 | + ) |
| 258 | +) |
| 259 | + |
| 260 | +# Structural region hover points |
| 261 | +for cx, hover_text in [ |
| 262 | + ((START_X0 + START_X1) / 2, "<b>START B Pattern</b><br>Signals Code 128 subset B (ASCII 32–127)<br>11 modules"), |
| 263 | + ((check_x0 + check_x1) / 2, f"<b>Check Digit: {checksum}</b><br>Modulo 103 of weighted symbol sum<br>11 modules"), |
| 264 | + ((stop_x0 + stop_x1) / 2, "<b>STOP Pattern</b><br>Terminates every Code 128 barcode<br>13 modules"), |
| 265 | +]: |
| 266 | + fig.add_trace( |
| 267 | + go.Scatter( |
| 268 | + x=[cx], |
| 269 | + y=[mid_bar_y], |
| 270 | + mode="markers", |
| 271 | + marker={"opacity": 0, "size": 20}, |
| 272 | + hovertemplate=f"{hover_text}<extra></extra>", |
| 273 | + showlegend=False, |
| 274 | + hoverlabel={"bgcolor": PAGE_BG, "bordercolor": INK_SOFT, "font": {"color": INK, "size": 13}}, |
| 275 | + ) |
| 276 | + ) |
| 277 | + |
| 278 | +# Human-readable text below barcode |
187 | 279 | fig.add_annotation( |
188 | 280 | x=total_width / 2, |
189 | | - y=50, |
| 281 | + y=45, |
190 | 282 | text=content, |
191 | 283 | showarrow=False, |
192 | | - font={"size": 36, "family": "Courier New, monospace", "color": "black"}, |
| 284 | + font={"size": 26, "family": "Courier New, monospace", "color": INK}, |
| 285 | + xanchor="center", |
| 286 | + yanchor="middle", |
| 287 | +) |
| 288 | + |
| 289 | +# Subset label above anatomy bracket |
| 290 | +fig.add_annotation( |
| 291 | + x=total_width / 2, |
| 292 | + y=600, |
| 293 | + text="Code 128B · ASCII Subset (32–127)", |
| 294 | + showarrow=False, |
| 295 | + font={"size": 18, "color": INK_SOFT}, |
193 | 296 | xanchor="center", |
194 | 297 | yanchor="middle", |
195 | 298 | ) |
196 | 299 |
|
197 | 300 | # Layout |
198 | 301 | fig.update_layout( |
199 | | - title={"text": "barcode-code128 · plotly · pyplots.ai", "font": {"size": 28}, "x": 0.5, "xanchor": "center"}, |
| 302 | + autosize=False, |
| 303 | + title={ |
| 304 | + "text": "barcode-code128 · python · plotly · anyplot.ai", |
| 305 | + "font": {"size": 16, "color": INK}, |
| 306 | + "x": 0.5, |
| 307 | + "xanchor": "center", |
| 308 | + }, |
200 | 309 | xaxis={"visible": False, "range": [0, total_width], "fixedrange": True}, |
201 | | - yaxis={"visible": False, "range": [0, 600], "fixedrange": True, "scaleanchor": "x", "scaleratio": 1}, |
202 | | - plot_bgcolor="white", |
203 | | - paper_bgcolor="white", |
204 | | - margin={"l": 50, "r": 50, "t": 100, "b": 50}, |
| 310 | + yaxis={"visible": False, "range": [0, 650], "fixedrange": True}, |
| 311 | + plot_bgcolor=PAGE_BG, |
| 312 | + paper_bgcolor=PAGE_BG, |
| 313 | + margin={"l": 80, "r": 40, "t": 80, "b": 60}, |
205 | 314 | showlegend=False, |
206 | 315 | ) |
207 | 316 |
|
208 | 317 | # Save |
209 | | -fig.write_image("plot.png", width=1600, height=900, scale=3) |
210 | | -fig.write_html("plot.html", include_plotlyjs="cdn") |
| 318 | +fig.write_image(f"plot-{THEME}.png", width=800, height=450, scale=4) |
| 319 | +fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn") |
0 commit comments