praca_magisterska/pytania/generate_pattern_diagrams.py

514 lines
21 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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!")