Skip to content

Commit 72a3122

Browse files
authored
FIX estimator HTML repr force dark or light color (#32330)
1 parent ff8aaf6 commit 72a3122

File tree

4 files changed

+85
-12
lines changed

4 files changed

+85
-12
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- Changes the way color are chosen when displaying an estimator as an HTML representation. Colors are not adapted anymore to the user's theme, but chosen based on theme declared color scheme (light or dark) for VSCode and JupyterLab. If theme does not declare a color scheme, scheme is chosen according to default text color of the page, if it fails fallbacks to a media query.
2+
By :user:`Matt J. <rouk1>`.

sklearn/utils/_repr_html/estimator.css

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,21 @@
1313
--sklearn-color-fitted-level-1: #d4ebff;
1414
--sklearn-color-fitted-level-2: #b3dbfd;
1515
--sklearn-color-fitted-level-3: cornflowerblue;
16+
}
1617

18+
#$id.light {
1719
/* Specific color for light theme */
18-
--sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));
19-
--sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));
20-
--sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));
20+
--sklearn-color-text-on-default-background: black;
21+
--sklearn-color-background: white;
22+
--sklearn-color-border-box: black;
2123
--sklearn-color-icon: #696969;
24+
}
2225

23-
@media (prefers-color-scheme: dark) {
24-
/* Redefinition of color scheme for dark theme */
25-
--sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));
26-
--sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));
27-
--sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));
28-
--sklearn-color-icon: #878787;
29-
}
26+
#$id.dark {
27+
--sklearn-color-text-on-default-background: white;
28+
--sklearn-color-background: #111;
29+
--sklearn-color-border-box: white;
30+
--sklearn-color-icon: #878787;
3031
}
3132

3233
#$id {

sklearn/utils/_repr_html/estimator.js

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,75 @@ document.querySelectorAll('.copy-paste-icon').forEach(function(element) {
3636
const toggleableContent = element.closest('.sk-toggleable__content');
3737
const paramPrefix = toggleableContent ? toggleableContent.dataset.paramPrefix : '';
3838
const paramName = element.parentElement.nextElementSibling
39-
.textContent.trim().split(' ')[0];
39+
.textContent.trim().split(' ')[0];
4040
const fullParamName = paramPrefix ? `${paramPrefix}${paramName}` : paramName;
4141

4242
element.setAttribute('title', fullParamName);
4343
});
44+
45+
46+
/**
47+
* Adapted from Skrub
48+
* https://github.com/skrub-data/skrub/blob/403466d1d5d4dc76a7ef569b3f8228db59a31dc3/skrub/_reporting/_data/templates/report.js#L789
49+
* @returns "light" or "dark"
50+
*/
51+
function detectTheme(element) {
52+
const body = document.querySelector('body');
53+
54+
// Check VSCode theme
55+
const themeKindAttr = body.getAttribute('data-vscode-theme-kind');
56+
const themeNameAttr = body.getAttribute('data-vscode-theme-name');
57+
58+
if (themeKindAttr && themeNameAttr) {
59+
const themeKind = themeKindAttr.toLowerCase();
60+
const themeName = themeNameAttr.toLowerCase();
61+
62+
if (themeKind.includes("dark") || themeName.includes("dark")) {
63+
return "dark";
64+
}
65+
if (themeKind.includes("light") || themeName.includes("light")) {
66+
return "light";
67+
}
68+
}
69+
70+
// Check Jupyter theme
71+
if (body.getAttribute('data-jp-theme-light') === 'false') {
72+
return 'dark';
73+
} else if (body.getAttribute('data-jp-theme-light') === 'true') {
74+
return 'light';
75+
}
76+
77+
// Guess based on a reference element's color
78+
const color = window.getComputedStyle(element, null).getPropertyValue('color');
79+
const match = color.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$/i);
80+
if (match) {
81+
const [r, g, b] = [match[1], match[2], match[3]];
82+
83+
// https://en.wikipedia.org/wiki/HSL_and_HSV#Lightness
84+
const luma = 0.299 * r + 0.587 * g + 0.114 * b;
85+
86+
if (luma > 180) {
87+
// If the text is very bright we have a dark theme
88+
return 'dark';
89+
}
90+
if (luma < 75) {
91+
// If the text is very dark we have a light theme
92+
return 'light';
93+
}
94+
// Otherwise fall back to the next heuristic.
95+
}
96+
97+
// Fallback to system preference
98+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
99+
}
100+
101+
102+
function forceTheme(elementId) {
103+
const estimatorElement = document.querySelector(`#${elementId}`);
104+
if (estimatorElement === null) {
105+
console.error(`Element with id ${elementId} not found.`);
106+
} else {
107+
const theme = detectTheme(estimatorElement);
108+
estimatorElement.classList.add(theme);
109+
}
110+
}

sklearn/utils/_repr_html/estimator.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,10 @@ def estimator_html_repr(estimator):
489489
with open(str(Path(__file__).parent / "estimator.js"), "r") as f:
490490
script = f.read()
491491

492-
html_end = f"</div></div><script>{script}</script></body>"
492+
html_end = (
493+
f"</div></div><script>{script}"
494+
f"\nforceTheme('{container_id}');</script></body>"
495+
)
493496

494497
out.write(html_end)
495498

0 commit comments

Comments
 (0)