11import { chartColors10 , chartColors20 , deepnoteBlues } from './colors' ;
2- import React , { memo , useLayoutEffect } from 'react' ;
2+ import React , { memo , useEffect , useLayoutEffect , useMemo , useState } from 'react' ;
33import { Vega } from 'react-vega' ;
44import { vega } from 'vega-embed' ;
5+ import { produce } from 'immer' ;
56
67import { numberFormats } from './number-formats' ;
8+ import { detectBaseTheme } from '../react-common/themeDetector' ;
79
810export interface VegaRendererProps {
911 spec : Record < string , unknown > ;
1012 renderer ?: 'svg' | 'canvas' ;
1113}
1214
15+ interface ThemeColors {
16+ backgroundColor : string ;
17+ foregroundColor : string ;
18+ isDark : boolean ;
19+ }
20+
21+ const getThemeColors = ( ) : ThemeColors => {
22+ const theme = detectBaseTheme ( ) ;
23+ const isDark = theme === 'vscode-dark' || theme === 'vscode-high-contrast' ;
24+ const styles = getComputedStyle ( document . body ) ;
25+ const backgroundColor = styles . getPropertyValue ( '--vscode-editor-background' ) . trim ( ) || 'transparent' ;
26+ const foregroundColor = styles . getPropertyValue ( '--vscode-editor-foreground' ) . trim ( ) || '#000000' ;
27+
28+ return { backgroundColor, foregroundColor, isDark } ;
29+ } ;
30+
31+ function useThemeColors ( ) : ThemeColors {
32+ const [ themeColors , setThemeColors ] = useState ( getThemeColors ) ;
33+
34+ useEffect ( ( ) => {
35+ const observer = new MutationObserver ( ( ) => {
36+ console . log ( 'Observed body change' )
37+ setThemeColors ( getThemeColors ( ) ) ;
38+ } ) ;
39+
40+ observer . observe ( document . body , {
41+ attributes : true ,
42+ attributeFilter : [ 'class' , 'data-vscode-theme-name' ]
43+ } ) ;
44+
45+ return ( ) => observer . disconnect ( ) ;
46+ } , [ ] ) ;
47+
48+ return themeColors ;
49+ }
50+
1351export const VegaRenderer = memo ( function VegaRenderer ( props : VegaRendererProps ) {
1452 const { renderer, spec } = props ;
1553
@@ -31,9 +69,48 @@ export const VegaRenderer = memo(function VegaRenderer(props: VegaRendererProps)
3169 vega . scheme ( 'deepnote_blues' , deepnoteBlues ) ;
3270 } , [ ] ) ;
3371
72+ const { backgroundColor, foregroundColor, isDark } = useThemeColors ( ) ;
73+ const themedSpec = useMemo ( ( ) => {
74+ const patchedSpec = produce ( spec , ( draft : any ) => {
75+ draft . background = backgroundColor ;
76+
77+ if ( ! draft . config ) {
78+ draft . config = { } ;
79+ }
80+
81+ draft . config . background = backgroundColor ;
82+
83+ if ( ! draft . config . axis ) {
84+ draft . config . axis = { } ;
85+ }
86+ draft . config . axis . domainColor = foregroundColor ;
87+ draft . config . axis . gridColor = isDark ? '#3e3e3e' : '#e0e0e0' ;
88+ draft . config . axis . tickColor = foregroundColor ;
89+ draft . config . axis . labelColor = foregroundColor ;
90+ draft . config . axis . titleColor = foregroundColor ;
91+
92+ if ( ! draft . config . legend ) {
93+ draft . config . legend = { } ;
94+ }
95+ draft . config . legend . labelColor = foregroundColor ;
96+ draft . config . legend . titleColor = foregroundColor ;
97+
98+ if ( ! draft . config . title ) {
99+ draft . config . title = { } ;
100+ }
101+ draft . config . title . color = foregroundColor ;
102+
103+ if ( ! draft . config . text ) {
104+ draft . config . text = { } ;
105+ }
106+ draft . config . text . color = foregroundColor ;
107+ } ) ;
108+ return structuredClone ( patchedSpec ) ; // Immer freezes the spec, which doesn't play well with Vega
109+ } , [ spec , backgroundColor , foregroundColor , isDark ] ) ;
110+
34111 return (
35112 < Vega
36- spec = { spec }
113+ spec = { themedSpec }
37114 renderer = { renderer }
38115 actions = { false }
39116 style = { {
0 commit comments