#!/usr/bin/env python3 """ Generate pattern cataloguing diagrams for PYTANIE 14 (AIS): 1. Pattern Template Structure — the standard fields every pattern has 2. Catalog Classification Map — catalogs arranged by scope & domain 3. Pattern Language Network — how patterns reference each other All: A4-compatible, B&W, 300 DPI, laser-printer-friendly. """ import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.patches import FancyBboxPatch, FancyArrowPatch import numpy as np import os DPI = 300 BG = 'white' LN = 'black' FS = 9 FS_TITLE = 13 FS_SMALL = 7.5 OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img') os.makedirs(OUTPUT_DIR, exist_ok=True) GRAY1 = '#E8E8E8' GRAY2 = '#D0D0D0' GRAY3 = '#B8B8B8' GRAY4 = '#F5F5F5' GRAY5 = '#C0C0C0' def draw_box(ax, x, y, w, h, text, fill='white', lw=1.2, fontsize=FS, fontweight='normal', ha='center', va='center', rounded=True): if rounded: rect = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.08", lw=lw, edgecolor=LN, facecolor=fill) else: rect = mpatches.Rectangle((x, y), w, h, lw=lw, edgecolor=LN, facecolor=fill) ax.add_patch(rect) ax.text(x + w / 2, y + h / 2, text, ha=ha, va=va, fontsize=fontsize, fontweight=fontweight, wrap=True) def draw_arrow(ax, x1, y1, x2, y2, lw=1.2, style='->', color=LN): ax.annotate("", xy=(x2, y2), xytext=(x1, y1), arrowprops=dict(arrowstyle=style, color=color, lw=lw)) # ============================================================ # 1. Pattern Template Structure (NaPSiRoKo mnemonic) # ============================================================ def generate_pattern_template(): fig, ax = plt.subplots(figsize=(8.27, 6)) ax.set_xlim(0, 10) ax.set_ylim(0, 8) ax.set_aspect('equal') ax.axis('off') fig.patch.set_facecolor(BG) ax.set_title('Szablon opisu wzorca \u2014 \u201eNaPSiRoKo\u201d', fontsize=FS_TITLE, fontweight='bold', pad=15) # Main card outline card_x, card_y, card_w, card_h = 1.5, 0.5, 7, 7 card = FancyBboxPatch((card_x, card_y), card_w, card_h, boxstyle="round,pad=0.15", lw=2.5, edgecolor=LN, facecolor=GRAY4) ax.add_patch(card) # Title of card ax.text(card_x + card_w / 2, card_y + card_h - 0.35, "KARTA WZORCA", ha='center', va='center', fontsize=FS_TITLE, fontweight='bold') # Fields as horizontal bands fields = [ ("Na", "NAZWA", "Layered, Observer, Microservices", GRAY1), ("P", "PROBLEM / KONTEKST", "Kiedy stosować? Jaki problem rozwiązuje?", 'white'), ("Si", "SIŁY (forces)", "Konkurencyjne wymagania do pogodzenia\n(np. testowalność vs wydajność)", GRAY1), ("Ro", "ROZWIĄZANIE", "Struktura, diagram, zachowanie", 'white'), ("Ko", "KONSEKWENCJE", "Tradeoffs: co zyskujemy, co tracimy", GRAY1), ] band_x = card_x + 0.3 band_w = card_w - 0.6 band_h = 1.05 start_y = card_y + card_h - 1.1 for i, (abbr, title, desc, fill) in enumerate(fields): by = start_y - i * (band_h + 0.15) # Abbreviation circle on the left circle = plt.Circle((band_x + 0.35, by + band_h / 2), 0.28, lw=1.5, edgecolor=LN, facecolor=GRAY2) ax.add_patch(circle) ax.text(band_x + 0.35, by + band_h / 2, abbr, ha='center', va='center', fontsize=10, fontweight='bold') # Field box fx = band_x + 0.8 fw = band_w - 0.8 rect = FancyBboxPatch((fx, by), fw, band_h, boxstyle="round,pad=0.06", lw=1, edgecolor=LN, facecolor=fill) ax.add_patch(rect) ax.text(fx + 0.15, by + band_h - 0.25, title, ha='left', va='center', fontsize=FS, fontweight='bold') ax.text(fx + 0.15, by + 0.25, desc, ha='left', va='center', fontsize=FS_SMALL, fontstyle='italic', color='#444444') # Arrow connecting fields if i < len(fields) - 1: draw_arrow(ax, band_x + 0.35, by - 0.02, band_x + 0.35, by - 0.13, lw=1.0) # Extra fields note at bottom ax.text(card_x + card_w / 2, card_y + 0.25, "+ Powiązane wzorce • Znane zastosowania • Warianty", ha='center', va='center', fontsize=FS_SMALL, fontstyle='italic') # Mnemonic reminder on the right ax.text(9.8, 4, "Mnemonik:\nNaPSiRoKo", ha='center', va='center', fontsize=10, fontweight='bold', rotation=90, color='#666666') fig.tight_layout() out = os.path.join(OUTPUT_DIR, 'q14_pattern_template.png') fig.savefig(out, dpi=DPI, bbox_inches='tight', facecolor=BG) plt.close(fig) print(f" Saved: {out}") # ============================================================ # 2. Catalog Classification Map # ============================================================ def generate_catalog_map(): fig, ax = plt.subplots(figsize=(8.27, 7)) ax.set_xlim(0, 12) ax.set_ylim(0, 9) ax.set_aspect('equal') ax.axis('off') fig.patch.set_facecolor(BG) ax.set_title('Mapa katalog\u00f3w wzorc\u00f3w \u2014 \u201ePawe\u0142 Gra\u0142 Efektownie Pod Chmurami\u201d', fontsize=FS_TITLE, fontweight='bold', pad=15) # Y-axis: Scale (architectural → design → idiom) ax.text(0.3, 7.8, "SKALA", fontsize=10, fontweight='bold', ha='center', va='center', rotation=90) ax.annotate("", xy=(0.3, 2.0), xytext=(0.3, 7.5), arrowprops=dict(arrowstyle='->', lw=1.5, color=LN)) scale_labels = [ (7.0, "Architektoniczny\n(cały system)"), (5.0, "Projektowy\n(klasa/obiekt)"), (3.0, "Idiomatyczny\n(linia kodu)"), ] for sy, label in scale_labels: ax.text(1.0, sy, label, fontsize=FS_SMALL, ha='left', va='center', fontstyle='italic') ax.plot([0.15, 0.45], [sy, sy], color=GRAY3, lw=0.8, ls='--') # X-axis: Domain ax.text(6.5, 1.2, "DOMENA ZASTOSOWANIA", fontsize=10, fontweight='bold', ha='center', va='center') ax.annotate("", xy=(11.5, 1.5), xytext=(2.0, 1.5), arrowprops=dict(arrowstyle='->', lw=1.5, color=LN)) # Catalog boxes positioned by scale × domain catalogs = [ # (x, y, w, h, name, subtitle, fill, mnemonic_letter) (2.5, 6.2, 2.5, 1.4, "POSA", "1996 • Buschmann\nLayers, Broker,\nPipes & Filters, MVC", GRAY1, "P"), (2.5, 4.2, 2.5, 1.4, "GoF", "1994 • Gamma et al.\n23 wzorce:\n5 kreac. / 7 strukt. / 11 behaw.", GRAY2, "G"), (5.5, 6.2, 2.5, 1.4, "EIP", "2003 • Hohpe & Woolf\nMessage Channel,\nRouter, Aggregator", GRAY1, "E"), (5.5, 4.2, 2.5, 1.4, "PoEAA", "2002 • M. Fowler\nRepository, Unit of Work,\nDomain Model", 'white', "P"), (8.5, 6.2, 2.8, 1.4, "Cloud\nPatterns", "~2015 • Azure/AWS\nCircuit Breaker,\nSaga, Sidecar", GRAY1, "C"), ] for cx, cy, cw, ch, name, sub, fill, ml in catalogs: rect = FancyBboxPatch((cx, cy), cw, ch, boxstyle="round,pad=0.1", lw=1.5, edgecolor=LN, facecolor=fill) ax.add_patch(rect) ax.text(cx + cw / 2, cy + ch - 0.3, name, ha='center', va='center', fontsize=10, fontweight='bold') ax.text(cx + cw / 2, cy + 0.4, sub, ha='center', va='center', fontsize=FS_SMALL, linespacing=1.3) # Mnemonic letter in corner circle = plt.Circle((cx + 0.25, cy + ch - 0.25), 0.2, lw=1, edgecolor=LN, facecolor=GRAY5) ax.add_patch(circle) ax.text(cx + 0.25, cy + ch - 0.25, ml, ha='center', va='center', fontsize=8, fontweight='bold') # Mnemonic bar at bottom mnem_y = 2.2 ax.text(6.0, mnem_y, "PGEP+C → Paweł Grał Efektownie Pod Chmurami", ha='center', va='center', fontsize=10, fontweight='bold', bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=LN, lw=1.5)) # Domain labels along x-axis domains = [ (3.75, 1.7, "Architektura"), (6.75, 1.7, "Integracja / Enterprise"), (9.9, 1.7, "Chmura"), ] for dx, dy, dlabel in domains: ax.text(dx, dy, dlabel, ha='center', va='center', fontsize=FS_SMALL, fontstyle='italic') fig.tight_layout() out = os.path.join(OUTPUT_DIR, 'q14_catalog_map.png') fig.savefig(out, dpi=DPI, bbox_inches='tight', facecolor=BG) plt.close(fig) print(f" Saved: {out}") # ============================================================ # 3. Three Pillars of Cataloguing # ============================================================ def generate_three_pillars(): fig, ax = plt.subplots(figsize=(8.27, 5.5)) ax.set_xlim(0, 12) ax.set_ylim(0, 7) ax.set_aspect('equal') ax.axis('off') fig.patch.set_facecolor(BG) ax.set_title("Jak są katalogowane wzorce? — Trzy filary", fontsize=FS_TITLE, fontweight='bold', pad=15) # Roof / banner roof_pts = np.array([[1, 5.5], [6, 6.8], [11, 5.5]]) roof = plt.Polygon(roof_pts, closed=True, lw=2, edgecolor=LN, facecolor=GRAY4) ax.add_patch(roof) ax.text(6, 6.0, "KATALOGOWANIE WZORCÓW", ha='center', va='center', fontsize=11, fontweight='bold') # Three pillars pillars = [ (1.3, "1. SZABLON\nOPISU", "Każdy wzorzec ma\nte same pola:\nNazwa → Problem\n→ Siły → Rozwiązanie\n→ Konsekwencje", "Analogia:\nformatka\nencyklopedii"), (4.8, "2. KLASYFIKACJA\nWIELOOSIOWA", "Osie podziału:\n• Skala (arch/proj/idiom)\n• Domena problemu\n• Atrybut jakościowy\n• Domena zastosowania", "Analogia:\nkategorie\nw bibliotece"), (8.3, "3. JĘZYK\nWZORCÓW", "Wzorce referują się\nwzajemnie tworząc\nsieć/graf:\nA → wymaga → B\nB → wariant → C", "Analogia:\n\u201ezobacz te\u017c\u201d\nw encyklopedii"), ] for px, title, desc, analogy in pillars: pw, ph = 2.8, 5.0 py = 0.5 # Pillar rectangle rect = FancyBboxPatch((px, py), pw, ph, boxstyle="round,pad=0.1", lw=1.8, edgecolor=LN, facecolor='white') ax.add_patch(rect) # Title ax.text(px + pw / 2, py + ph - 0.55, title, ha='center', va='center', fontsize=9, fontweight='bold') # Horizontal line under title ax.plot([px + 0.2, px + pw - 0.2], [py + ph - 1.0, py + ph - 1.0], color=LN, lw=0.8) # Description ax.text(px + pw / 2, py + ph / 2 - 0.3, desc, ha='center', va='center', fontsize=FS_SMALL, linespacing=1.4) # Analogy box at bottom analogy_rect = FancyBboxPatch((px + 0.2, py + 0.15), pw - 0.4, 1.0, boxstyle="round,pad=0.06", lw=0.8, edgecolor=GRAY3, facecolor=GRAY1) ax.add_patch(analogy_rect) ax.text(px + pw / 2, py + 0.65, analogy, ha='center', va='center', fontsize=FS_SMALL, fontstyle='italic', color='#555555') fig.tight_layout() out = os.path.join(OUTPUT_DIR, 'q14_three_pillars.png') fig.savefig(out, dpi=DPI, bbox_inches='tight', facecolor=BG) plt.close(fig) print(f" Saved: {out}") # ============================================================ # 4. Filled-in Observer Pattern Card # ============================================================ def generate_observer_card_filled(): fig, ax = plt.subplots(figsize=(8.27, 8.5)) ax.set_xlim(0, 10) ax.set_ylim(0, 10) ax.set_aspect('equal') ax.axis('off') fig.patch.set_facecolor(BG) ax.set_title('Wypełniona karta wzorca — Observer (GoF)', fontsize=FS_TITLE, fontweight='bold', pad=15) # Main card outline card_x, card_y, card_w, card_h = 0.8, 0.3, 8.4, 9.2 card = FancyBboxPatch((card_x, card_y), card_w, card_h, boxstyle="round,pad=0.15", lw=2.5, edgecolor=LN, facecolor=GRAY4) ax.add_patch(card) # Fields with actual Observer content fields = [ ("Na", "NAZWA", "Observer", GRAY2, True), ("P", "PROBLEM", "Obiekt (Subject) zmienia stan → wielu zależnych\n" "obiektów musi zareagować, ale Subject nie\n" "powinien znać ich konkretnych typów.", GRAY1, False), ("Si", "SIŁY", "• loose coupling (nie znać obserwatorów z nazwy)\n" " vs koszt powiadomień (N obserwatorów = N wywołań)\n" "• otwartość na rozszerzenia vs złożoność debugowania", 'white', False), ("Ro", "ROZWIĄZANIE", "Subject przechowuje listę Observer.\n" "Metody: attach(o), detach(o), notify().\n" "notify() iteruje po liście i woła update()\n" "na każdym obserwatorze.", GRAY1, False), ("Ko", "KONSEKWENCJE", "(+) Luźne wiązanie — Subject ↔ Observer\n" "(+) Nowi obserwatorzy bez zmian w Subject\n" "(−) Kaskada powiadomień może być kosztowna\n" "(−) Memory leaks jeśli nie detach()", 'white', False), ] band_x = card_x + 0.3 band_w = card_w - 0.6 start_y = card_y + card_h - 0.65 for i, (abbr, title, content, fill, is_title_field) in enumerate(fields): if is_title_field: band_h = 0.7 elif i == 1: band_h = 1.3 elif i == 2: band_h = 1.4 elif i == 3: band_h = 1.5 else: band_h = 1.5 by = start_y - sum( (0.7 if j == 0 else 1.3 if j == 1 else 1.4 if j == 2 else 1.5 if j == 3 else 1.5) + 0.15 for j in range(i) ) # Abbreviation circle circle = plt.Circle((band_x + 0.35, by + band_h / 2), 0.28, lw=1.5, edgecolor=LN, facecolor=GRAY3) ax.add_patch(circle) ax.text(band_x + 0.35, by + band_h / 2, abbr, ha='center', va='center', fontsize=10, fontweight='bold') # Field box fx = band_x + 0.8 fw = band_w - 0.8 rect = FancyBboxPatch((fx, by), fw, band_h, boxstyle="round,pad=0.06", lw=1, edgecolor=LN, facecolor=fill) ax.add_patch(rect) if is_title_field: ax.text(fx + fw / 2, by + band_h / 2, f"{title}: {content}", ha='center', va='center', fontsize=12, fontweight='bold') else: ax.text(fx + 0.15, by + band_h - 0.2, title, ha='left', va='center', fontsize=FS, fontweight='bold') ax.text(fx + 0.15, by + band_h / 2 - 0.15, content, ha='left', va='center', fontsize=FS_SMALL, family='monospace', linespacing=1.3) # Arrow if i < len(fields) - 1: draw_arrow(ax, band_x + 0.35, by - 0.02, band_x + 0.35, by - 0.13, lw=1.0) # Extra info at bottom extra_y = 0.55 extras = [ "Powiązane: Mediator (centralizuje), Pub/Sub (rozproszony), MVC (View = Observer)", "Znane użycia: Java Swing listeners, C# event/delegate, React useState, DOM addEventListener" ] for j, txt in enumerate(extras): ax.text(card_x + card_w / 2, extra_y + (1 - j) * 0.25, txt, ha='center', va='center', fontsize=FS_SMALL, fontstyle='italic', color='#444444') fig.tight_layout() out = os.path.join(OUTPUT_DIR, 'q14_observer_card_filled.png') fig.savefig(out, dpi=DPI, bbox_inches='tight', facecolor=BG) plt.close(fig) print(f" Saved: {out}") # ============================================================ # 5. Pattern Language Navigation Graph # ============================================================ def generate_pattern_language_navigation(): fig, ax = plt.subplots(figsize=(8.27, 9)) ax.set_xlim(0, 12) ax.set_ylim(0, 12) ax.set_aspect('equal') ax.axis('off') fig.patch.set_facecolor(BG) ax.set_title('Język wzorców — nawigacja „problem → wzorzec → nowy problem"', fontsize=FS_TITLE, fontweight='bold', pad=15) # Node positions: (x, y, label, is_pattern, fill) # Left column: problems; Right column: patterns nodes = [ # Problems (left, rounded rect, white) (1.5, 10.5, "Monolith\nnie skaluje się", False, 'white'), (1.5, 8.2, "Jak routować\nżądania do\nserwisów?", False, 'white'), (1.5, 5.9, "Co gdy serwis\nnie odpowiada?", False, 'white'), (1.5, 3.6, "Jak zachować\nspójność\ntransakcji?", False, 'white'), (1.5, 1.3, "Jak odnaleźć\nadres serwisu?", False, 'white'), # Patterns (right, filled rect, gray) (7.0, 9.3, "Microservices", True, GRAY2), (7.0, 7.0, "API Gateway", True, GRAY2), (7.0, 4.7, "Circuit Breaker", True, GRAY2), (7.0, 2.4, "Saga", True, GRAY2), (10.0, 5.9, "Service\nDiscovery", True, GRAY1), ] # Draw nodes node_w_prob = 2.8 node_h_prob = 1.3 node_w_pat = 2.5 node_h_pat = 1.0 for nx, ny, label, is_pattern, fill in nodes: if is_pattern: w, h = node_w_pat, node_h_pat rect = FancyBboxPatch((nx - w/2, ny - h/2), w, h, boxstyle="round,pad=0.1", lw=2, edgecolor=LN, facecolor=fill) ax.add_patch(rect) ax.text(nx, ny, label, ha='center', va='center', fontsize=10, fontweight='bold') else: w, h = node_w_prob, node_h_prob rect = FancyBboxPatch((nx - w/2, ny - h/2), w, h, boxstyle="round,pad=0.1", lw=1.2, edgecolor=LN, facecolor=fill, linestyle='--') ax.add_patch(rect) ax.text(nx, ny, label, ha='center', va='center', fontsize=FS_SMALL, fontstyle='italic') # Arrows: problem → pattern (solid), pattern → problem (dashed label) arrows = [ # (x1, y1, x2, y2, label, style) (2.9, 10.5, 5.75, 9.5, "rozwiązuje →", '->', 1.5), (7.0, 8.8, 2.9, 8.5, "← rodzi problem", '->', 1.0), (2.9, 8.0, 5.75, 7.2, "rozwiązuje →", '->', 1.5), (7.0, 6.5, 2.9, 6.2, "← rodzi problem", '->', 1.0), (2.9, 5.7, 5.75, 5.0, "rozwiązuje →", '->', 1.5), (7.0, 4.2, 2.9, 3.9, "← rodzi problem", '->', 1.0), (2.9, 3.3, 5.75, 2.6, "rozwiązuje →", '->', 1.5), # Microservices → Service Discovery (8.25, 9.0, 9.5, 6.5, "wymaga →", '->', 1.0), # Problem → Service Discovery (2.9, 1.3, 8.75, 5.6, "rozwiązuje →", '->', 1.2), ] for x1, y1, x2, y2, label, style, lw in arrows: ax.annotate("", xy=(x2, y2), xytext=(x1, y1), arrowprops=dict(arrowstyle=style, color=LN, lw=lw, connectionstyle="arc3,rad=0.05")) mx, my = (x1 + x2) / 2, (y1 + y2) / 2 ax.text(mx, my + 0.2, label, ha='center', va='center', fontsize=6.5, fontstyle='italic', color='#555555', bbox=dict(boxstyle='round,pad=0.1', facecolor='white', edgecolor='none', alpha=0.8)) # Legend legend_y = 0.3 # Problem node r1 = FancyBboxPatch((1.0, legend_y - 0.2), 1.5, 0.4, boxstyle="round,pad=0.05", lw=1, edgecolor=LN, facecolor='white', linestyle='--') ax.add_patch(r1) ax.text(1.75, legend_y, "Problem", ha='center', va='center', fontsize=7) # Pattern node r2 = FancyBboxPatch((3.5, legend_y - 0.2), 1.5, 0.4, boxstyle="round,pad=0.05", lw=1.5, edgecolor=LN, facecolor=GRAY2) ax.add_patch(r2) ax.text(4.25, legend_y, "Wzorzec", ha='center', va='center', fontsize=7, fontweight='bold') ax.text(6.5, legend_y, "Nawigacja: Problem → Wzorzec → Nowy Problem → Wzorzec → ...", ha='left', va='center', fontsize=7, fontstyle='italic') fig.tight_layout() out = os.path.join(OUTPUT_DIR, 'q14_pattern_language_navigation.png') fig.savefig(out, dpi=DPI, bbox_inches='tight', facecolor=BG) plt.close(fig) print(f" Saved: {out}") # ============================================================ # Main # ============================================================ if __name__ == '__main__': print("Generating PYTANIE 14 diagrams...") generate_pattern_template() generate_catalog_map() generate_three_pillars() generate_observer_card_filled() generate_pattern_language_navigation() print("Done!")