diff --git a/app.py b/app.py index 8ca2e2f..6ca3a99 100644 --- a/app.py +++ b/app.py @@ -885,30 +885,30 @@ def get_admin_expense_summary(): } -import hashlib, colorsys, math +# pip install hsluv +import hashlib +import hsluv # HSLuv - human-friendly, approx. perceptually uniform -def rehash32(x: int) -> int: - # Wang/Jenkins mix – lepsza dystrybucja bitów - x = (x ^ 61) ^ (x >> 16) - x = x + (x << 3) - x = x ^ (x >> 4) - x = x * 0x27d4eb2d - x = x ^ (x >> 15) - return x & 0xFFFFFFFF +def category_to_color_hsluv(name: str) -> str: + hv = int(hashlib.md5(name.encode("utf-8")).hexdigest(), 16) -def category_to_color(name: str, buckets: int = 72) -> str: - # buckets: co 5° (72*5=360) – równy podział koła, pełna rozpiętość - h = int(hashlib.md5(name.encode("utf-8")).hexdigest(), 16) - h32 = rehash32(h & 0xFFFFFFFF) - bucket = (h32 % buckets) - # jitter ±2° w obrębie bucketu, by rozbić kolizje - jitter = ((rehash32(h32) % 5) - 2) # -2..+2 - hue_deg = (bucket * (360 // buckets) + jitter) % 360 - # Stałe S/L w bezpiecznym paśmie (pełna „kolorowość”, dobra czytelność) - s = 0.72 - l = 0.52 - r, g, b = colorsys.hls_to_rgb(hue_deg/360.0, l, s) - return f"#{int(r*255):02x}{int(g*255):02x}{int(b*255):02x}" + # Pełne pokrycie hue 0..360 + h = hv % 360 + + # Trzymaj stałe L i S (HSLuv: S≈chroma), dobrane dla dobrej widoczności + # L w [50..65] jest uniwersalne na jasnym i ciemnym tle + l = 60.0 + s = 85.0 + + # Drobna, ograniczona wariacja (±3) aby rozbić kolizje + s += ((hv >> 17) % 7) - 3 + l += ((hv >> 23) % 7) - 3 + + s = min(95.0, max(70.0, s)) + l = min(68.0, max(52.0, l)) + + r, g, b = hsluv.hsluv_to_rgb([h, s, l]) # zwraca RGB w [0..1] + return f"#{int(round(r*255)):02x}{int(round(g*255)):02x}{int(round(b*255)):02x}" def get_total_expenses_grouped_by_category(