Skip to content

Commit 0fbf5a4

Browse files
feat(#1543): adopt the DataJoint brand tier colors in diagram themes
Retunes _DIAGRAM_THEMES to the brand palette: Imported on DataJoint Blue #00A0DF, Computed on DataJoint Orange #FF5113, brand grey Lookup, retuned Manual green; edges, cluster frames, and titles on brand navy #171C39. Dark theme derived by the existing rule (stroke hue kept, fills inverted toward navy-tinted darks, text brightened to AA on #161A21). Hue families are preserved, so tier recognition carries over; structural work from #1534 (shapes, clusters, underlines, edge weights, adaptive block) is unchanged. Two collision-free adjustments, commented in place: Part text #55585C (vs Lookup's #5A5C5F) and schema-cluster frame #171D3A (vs edge navy) so the adaptive mapping stays one-light-color-one-role. Adds test_theme_text_contrast_meets_aa (every text-on-fill pair >= 4.5:1 in both themes) and test_adaptive_mapping_is_collision_free; updates theme and mermaid expectations. Closes #1543
1 parent c1aeb19 commit 0fbf5a4

2 files changed

Lines changed: 62 additions & 21 deletions

File tree

src/datajoint/diagram.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -73,28 +73,34 @@
7373
bg=None,
7474
palette={
7575
None: ("#FFFDE7", "#C9BC5B", "#6B6420"),
76-
Manual: ("#E7F3EC", "#2F7D5B", "#1B5138"),
77-
Lookup: ("#F2F4F7", "#A9B1BD", "#495261"),
78-
Computed: ("#FBEAEC", "#B23A48", "#7C2430"),
79-
Imported: ("#E2ECFA", "#2A5FA5", "#123A6D"),
80-
Part: ("#FFFFFF", "#9AA6B8", "#46536B"),
76+
Manual: ("#E8F0E9", "#3E7A52", "#28513A"),
77+
Lookup: ("#F0F0F1", "#808285", "#5A5C5F"),
78+
Computed: ("#FFEDE5", "#FF5113", "#B23200"),
79+
Imported: ("#E0F4FC", "#00A0DF", "#00537A"),
80+
# Part text is #55585C rather than Lookup's #5A5C5F only so every
81+
# light color maps to exactly one role (the adaptive block requires
82+
# a collision-free mapping); visually identical.
83+
Part: ("#FFFFFF", "#B9BBBE", "#55585C"),
8184
"collapsed": ("#EDEEF0", "#808890", "#404040"),
8285
},
83-
edge="#3A424F",
86+
# Edges and titles are brand navy. The schema-cluster frame is #171D3A —
87+
# one step off navy for the same collision-free reason (edge and frame
88+
# are both strokes and need distinct dark counterparts).
89+
edge="#171C39",
8490
edge_renamed="#C77D3A",
8591
edge_alpha="9E",
86-
schema_cluster=("gray", "gray"),
92+
schema_cluster=("#171D3A", "#171C39"),
8793
entity_fill="#F3F5F8",
8894
),
8995
"dark": dict(
9096
bg="#161A21",
9197
palette={
9298
None: ("#3A3620", "#C9BC5B", "#EBE3A0"),
93-
Manual: ("#16281F", "#4FA97F", "#BCE6CF"),
94-
Lookup: ("#242832", "#8A93A1", "#C9CFD9"),
95-
Computed: ("#331A1F", "#D0687A", "#F3C2CB"),
96-
Imported: ("#152538", "#5E92D6", "#C3DAF6"),
97-
Part: ("#1E232C", "#7B879B", "#C4CCDB"),
99+
Manual: ("#16281F", "#6BBF94", "#BCE6CF"),
100+
Lookup: ("#242832", "#9DA0A4", "#C9CFD9"),
101+
Computed: ("#331B12", "#FF7A47", "#FFC7AE"),
102+
Imported: ("#0F2433", "#33B8E8", "#BEE7F9"),
103+
Part: ("#1E232C", "#8F959D", "#C8CED8"),
98104
"collapsed": ("#242730", "#8890A0", "#C7CDD6"),
99105
},
100106
edge="#AEB6C2",
@@ -1811,7 +1817,7 @@ def make_mermaid(self) -> str:
18111817
--------
18121818
>>> print(dj.Diagram(schema).make_mermaid())
18131819
flowchart TB
1814-
classDef manual fill:#E7F3EC,stroke:#2F7D5B,color:#1B5138
1820+
classDef manual fill:#E8F0E9,stroke:#3E7A52,color:#28513A
18151821
...
18161822
subgraph my_pipeline["my_pipeline"]
18171823
Mouse[Mouse]:::manual
@@ -1820,7 +1826,7 @@ def make_mermaid(self) -> str:
18201826
end
18211827
Mouse --> Session
18221828
Session --> Neuron
1823-
linkStyle 0 stroke:#3A424F,stroke-width:1px
1829+
linkStyle 0 stroke:#171C39,stroke-width:1px
18241830
"""
18251831
graph = self._make_graph()
18261832
direction = self._connection._config.display.diagram_direction

tests/integration/test_diagram_style.py

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ def test_light_theme_style(schema_by_backend):
7979
ctx = _build(schema_by_backend)
8080
svg = _svg(schema_by_backend, ctx, "light")
8181
# tier fills
82-
for fill in ("#e7f3ec", "#f2f4f7", "#e2ecfa", "#fbeaec", "#ffffff"):
82+
for fill in ("#e8f0e9", "#f0f0f1", "#e0f4fc", "#ffede5", "#ffffff"):
8383
assert fill in svg, f"light tier fill {fill} missing"
8484
# a couple tier strokes
85-
for stroke in ("#2f7d5b", "#b23a48"):
85+
for stroke in ("#3e7a52", "#ff5113"):
8686
assert stroke in svg, f"light tier stroke {stroke} missing"
8787
# thick (1:1) and thin (multi) edge weights both present
8888
assert 'stroke-width="2"' in svg, "thick (1:1) edge missing"
@@ -97,7 +97,7 @@ def test_dark_theme_style(schema_by_backend):
9797
ctx = _build(schema_by_backend)
9898
svg = _svg(schema_by_backend, ctx, "dark")
9999
assert "#161a21" in svg, "dark background missing"
100-
for fill in ("#16281f", "#152538", "#331a1f"):
100+
for fill in ("#16281f", "#0f2433", "#331b12"):
101101
assert fill in svg, f"dark tier fill {fill} missing"
102102

103103

@@ -108,7 +108,7 @@ def test_auto_theme_is_adaptive(schema_by_backend):
108108
svg = _svg(schema_by_backend, ctx, "auto")
109109
assert "@media (prefers-color-scheme: dark)" in svg, "auto theme must inject the adaptive media block"
110110
# base render is light; the media block maps a light color to its dark counterpart
111-
assert "#e7f3ec" in svg and "#16281f" in svg, "auto theme must carry both light base and dark override colors"
111+
assert "#e8f0e9" in svg and "#16281f" in svg, "auto theme must carry both light base and dark override colors"
112112

113113

114114
def test_mermaid_matches_modernized_notation(schema_by_backend):
@@ -138,8 +138,8 @@ class Analysis(dj.Computed):
138138
mmd = dj.Diagram(schema, context=ctx).make_mermaid()
139139

140140
# shared light-theme palette (classDefs), not the old bright colors
141-
assert "classDef manual fill:#E7F3EC,stroke:#2F7D5B" in mmd
142-
assert "classDef computed fill:#FBEAEC,stroke:#B23A48" in mmd
141+
assert "classDef manual fill:#E8F0E9,stroke:#3E7A52" in mmd
142+
assert "classDef computed fill:#FFEDE5,stroke:#FF5113" in mmd
143143
assert "#90EE90" not in mmd, "old bright palette must be gone"
144144

145145
# cardinality edge weights via linkStyle: thick 1:1 and thin one-to-many both present
@@ -150,7 +150,42 @@ class Analysis(dj.Computed):
150150

151151
# renamed FK takes the theme's amber; ordinary edges the slate
152152
assert "stroke:#C77D3A" in mmd, "renamed-FK amber missing"
153-
assert "stroke:#3A424F" in mmd, "ordinary edge slate missing"
153+
assert "stroke:#171C39" in mmd, "ordinary edge navy missing"
154154

155155
# master-part group nests in an entity subgraph
156156
assert "subgraph entity_" in mmd, "master-part entity nesting missing"
157+
158+
159+
def _contrast(fg, bg):
160+
def lum(h):
161+
c = [int(h.lstrip("#")[i : i + 2], 16) / 255 for i in (0, 2, 4)]
162+
c = [x / 12.92 if x <= 0.03928 else ((x + 0.055) / 1.055) ** 2.4 for x in c]
163+
return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]
164+
165+
hi, lo = sorted([lum(fg), lum(bg)], reverse=True)
166+
return (hi + 0.05) / (lo + 0.05)
167+
168+
169+
def test_theme_text_contrast_meets_aa():
170+
"""Every tier's text-on-fill pair holds WCAG AA (>= 4.5:1) in both themes (#1543)."""
171+
for name, theme in dj.diagram._DIAGRAM_THEMES.items():
172+
for tier, (fill, _stroke, text) in theme["palette"].items():
173+
ratio = _contrast(text, fill)
174+
assert ratio >= 4.5, f"{name}/{tier}: {text} on {fill} = {ratio:.2f} < 4.5"
175+
176+
177+
def test_adaptive_mapping_is_collision_free():
178+
"""Every light color maps to exactly one dark counterpart per attribute (#1532 invariant)."""
179+
light, dark = dj.diagram._DIAGRAM_THEMES["light"], dj.diagram._DIAGRAM_THEMES["dark"]
180+
for idx, kind in ((0, "fill"), (1, "stroke"), (2, "fill")):
181+
mapping = {}
182+
for tier in light["palette"]:
183+
lv, dv = light["palette"][tier][idx].lower(), dark["palette"][tier][idx]
184+
assert mapping.setdefault((kind, lv), dv) == dv, f"collision on {kind} {lv}"
185+
strokes = {}
186+
for lv, dv in [
187+
(light["edge"].lower(), dark["edge"]),
188+
(light["edge_renamed"].lower(), dark["edge_renamed"]),
189+
(light["schema_cluster"][0].lower(), dark["schema_cluster"][0]),
190+
]:
191+
assert strokes.setdefault(lv, dv) == dv, f"stroke collision on {lv}"

0 commit comments

Comments
 (0)