2026-02-17 21:08:36 +01:00
|
|
|
|
#!/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}")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-21 19:51:31 +01:00
|
|
|
|
# ============================================================
|
|
|
|
|
|
# 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}")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-17 21:08:36 +01:00
|
|
|
|
# ============================================================
|
|
|
|
|
|
# Main
|
|
|
|
|
|
# ============================================================
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
|
print("Generating PYTANIE 14 diagrams...")
|
|
|
|
|
|
generate_pattern_template()
|
|
|
|
|
|
generate_catalog_map()
|
|
|
|
|
|
generate_three_pillars()
|
2026-02-21 19:51:31 +01:00
|
|
|
|
generate_observer_card_filled()
|
|
|
|
|
|
generate_pattern_language_navigation()
|
2026-02-17 21:08:36 +01:00
|
|
|
|
print("Done!")
|