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' ;
9+ import type { Spec as VegaSpec } from 'vega' ;
710
811export interface VegaRendererProps {
9- spec : Record < string , unknown > ;
12+ spec : VegaSpec ;
1013 renderer ?: 'svg' | 'canvas' ;
1114}
1215
16+ interface ThemeColors {
17+ backgroundColor : string ;
18+ foregroundColor : string ;
19+ isDark : boolean ;
20+ }
21+
22+ const getThemeColors = ( ) : ThemeColors => {
23+ const theme = detectBaseTheme ( ) ;
24+ const isDark = theme === 'vscode-dark' || theme === 'vscode-high-contrast' ;
25+ const styles = getComputedStyle ( document . body ) ;
26+ const backgroundColor = styles . getPropertyValue ( '--vscode-editor-background' ) . trim ( ) || 'transparent' ;
27+ const foregroundColor = styles . getPropertyValue ( '--vscode-editor-foreground' ) . trim ( ) || '#000000' ;
28+
29+ return { backgroundColor, foregroundColor, isDark } ;
30+ } ;
31+
32+ function useThemeColors ( ) : ThemeColors {
33+ const [ themeColors , setThemeColors ] = useState ( getThemeColors ) ;
34+
35+ useEffect ( ( ) => {
36+ const observer = new MutationObserver ( ( ) => {
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,58 @@ 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 ) => {
75+ draft . background = backgroundColor ;
76+
77+ if ( ! draft . config ) {
78+ draft . config = { } ;
79+ }
80+
81+ draft . config . background = backgroundColor ;
82+
83+ if ( ! draft . config . style ) {
84+ draft . config . style = { } ;
85+ }
86+ if ( ! draft . config . style . cell ) {
87+ draft . config . style . cell = { } ;
88+ }
89+ draft . config . style . cell = {
90+ stroke : backgroundColor
91+ } ;
92+
93+ if ( ! draft . config . axis ) {
94+ draft . config . axis = { } ;
95+ }
96+ draft . config . axis . domainColor = foregroundColor ;
97+ draft . config . axis . gridColor = isDark ? '#3e3e3e' : '#e0e0e0' ;
98+ draft . config . axis . tickColor = foregroundColor ;
99+ draft . config . axis . labelColor = foregroundColor ;
100+ draft . config . axis . titleColor = foregroundColor ;
101+
102+ if ( ! draft . config . legend ) {
103+ draft . config . legend = { } ;
104+ }
105+ draft . config . legend . labelColor = foregroundColor ;
106+ draft . config . legend . titleColor = foregroundColor ;
107+
108+ if ( ! draft . config . title ) {
109+ draft . config . title = { } ;
110+ }
111+ draft . config . title . color = foregroundColor ;
112+
113+ if ( ! draft . config . text ) {
114+ draft . config . text = { } ;
115+ }
116+ draft . config . text . fill = foregroundColor ;
117+ } ) ;
118+ return structuredClone ( patchedSpec ) ; // Immer freezes the spec, which doesn't play well with Vega
119+ } , [ spec , backgroundColor , foregroundColor , isDark ] ) ;
120+
34121 return (
35122 < Vega
36- spec = { spec }
123+ spec = { themedSpec }
37124 renderer = { renderer }
38125 actions = { false }
39126 style = { {
0 commit comments