@@ -89,26 +89,93 @@ def _render_fig_to_png(figure, dpi=preview_dpi):
8989 'Ready — make edits on the right panel</span>'
9090 )
9191
92+ # ---- Render mode: Auto vs Manual ----
93+ _render_state = {'pending' : 0 }
94+
95+ render_mode = widgets .ToggleButtons (
96+ options = ['Auto' , 'Manual' ],
97+ value = 'Auto' ,
98+ description = '' ,
99+ tooltips = [
100+ 'Re-render preview after every edit' ,
101+ 'Batch edits, then click Apply to re-render' ,
102+ ],
103+ button_style = '' ,
104+ layout = widgets .Layout (width = 'auto' ),
105+ )
106+ apply_btn = widgets .Button (
107+ description = f'Apply' ,
108+ button_style = 'warning' ,
109+ icon = 'refresh' ,
110+ layout = widgets .Layout (width = 'auto' , display = 'none' ),
111+ )
112+
113+ def _do_render (figure ):
114+ """Actually render the figure to the preview widget."""
115+ render_dpi = int (figure .dpi )
116+ fig_image .value = _render_fig_to_png (figure , dpi = render_dpi )
117+ n = len (editor .edits )
118+ w_in , h_in = figure .get_size_inches ()
119+ dpi = int (figure .dpi )
120+ px_w , px_h = int (w_in * dpi ), int (h_in * dpi )
121+ _render_state ['pending' ] = 0
122+ apply_btn .description = 'Apply'
123+ status_bar .value = (
124+ f'<span style="font-size:11px; color:#2ECC71">'
125+ f'{ n } edit(s) — DPI: { dpi } → export size: { px_w } ×{ px_h } px</span>'
126+ )
127+
92128 def _live_refresh (figure ):
93- """Callback: re-render the figure into the Image widget ."""
129+ """Callback: re-render or queue depending on render mode ."""
94130 try :
95- # Use figure's actual DPI for preview (matches user setting)
96- render_dpi = int (figure .dpi )
97- fig_image .value = _render_fig_to_png (figure , dpi = render_dpi )
98- n = len (editor .edits )
99- status_bar .value = (
100- f'<span style="font-size:11px; color:#2ECC71">'
101- f'{ n } edit(s) — preview updated (DPI: { int (figure .dpi )} )</span>'
102- )
131+ if render_mode .value == 'Auto' :
132+ _do_render (figure )
133+ else :
134+ # Manual mode: count pending edits, don't re-render
135+ _render_state ['pending' ] += 1
136+ p = _render_state ['pending' ]
137+ n = len (editor .edits )
138+ apply_btn .description = f'Apply ({ p } )'
139+ status_bar .value = (
140+ f'<span style="font-size:11px; color:#F39C12">'
141+ f'{ n } edit(s) — { p } pending re-render '
142+ f'(click Apply)</span>'
143+ )
103144 except Exception :
104145 pass
105146
147+ def _on_apply (btn ):
148+ _do_render (editor .fig )
149+
150+ apply_btn .on_click (_on_apply )
151+
152+ def _on_render_mode (change ):
153+ if change ['new' ] == 'Manual' :
154+ apply_btn .layout .display = ''
155+ else :
156+ apply_btn .layout .display = 'none'
157+ # Switching back to Auto: render immediately if pending
158+ if _render_state ['pending' ] > 0 :
159+ _do_render (editor .fig )
160+
161+ render_mode .observe (_on_render_mode , names = 'value' )
162+
106163 # Register the live refresh callback
107164 editor .on_refresh (_live_refresh )
108165
109- # Preview container (figure + status bar)
166+ render_bar = widgets .HBox (
167+ [render_mode , apply_btn ],
168+ layout = widgets .Layout (
169+ justify_content = 'flex-start' ,
170+ align_items = 'center' ,
171+ gap = '8px' ,
172+ ),
173+ )
174+
175+ # Preview container (figure + render controls + status bar)
110176 fig_container = widgets .VBox ([
111177 fig_image ,
178+ render_bar ,
112179 status_bar ,
113180 ], layout = widgets .Layout (
114181 flex = '1 1 auto' ,
@@ -631,6 +698,11 @@ def _cb(change):
631698 layout = widgets .Layout (width = '95%' ),
632699 continuous_update = False ,
633700 )
701+ dpi_hint = widgets .HTML (
702+ '<span style="font-size:11px; color:#888">'
703+ 'DPI affects export/save resolution. '
704+ 'Preview is scaled to fit — visual size won\' t change.</span>'
705+ )
634706
635707 # ---- Legend controls ----
636708 legend_visible = widgets .Checkbox (
@@ -810,6 +882,8 @@ def _on_figsize_h(change):
810882
811883 def _on_dpi (change ):
812884 editor .set_dpi (change ['new' ])
885+ # Sync save DPI to match figure DPI
886+ save_dpi .value = change ['new' ]
813887
814888 fig_dpi .observe (_on_dpi , names = 'value' )
815889
@@ -911,7 +985,7 @@ def _on_ylim(change):
911985 widgets .HTML ('<hr style="margin:4px 0">' ),
912986 widgets .HTML ('<b>Figure Size</b>' ),
913987 size_preset , fig_width , fig_height ,
914- fig_dpi ,
988+ fig_dpi , dpi_hint ,
915989 widgets .HTML ('<hr style="margin:4px 0">' ),
916990 widgets .HTML ('<b>Background</b>' ),
917991 fig_bg_color , ax_bg_color ,
@@ -1024,7 +1098,12 @@ def _on_theme(change):
10241098 )
10251099 save_dpi = widgets .IntSlider (
10261100 value = 300 , min = 72 , max = 600 , step = 50 ,
1027- description = 'DPI:' , layout = widgets .Layout (width = '95%' ),
1101+ description = 'Save DPI:' , layout = widgets .Layout (width = '95%' ),
1102+ )
1103+ save_dpi_hint = widgets .HTML (
1104+ '<span style="font-size:11px; color:#888">'
1105+ 'Resolution for exported file. '
1106+ 'Synced from Figure DPI in Layout tab.</span>'
10281107 )
10291108 save_btn = widgets .Button (
10301109 description = 'Save Figure' ,
@@ -1109,7 +1188,7 @@ def _on_copy(btn):
11091188 code_tab = widgets .VBox ([
11101189 widgets .HTML ('<b>Export & Code</b>' ),
11111190 widgets .HBox ([save_filename , save_btn ]),
1112- save_dpi ,
1191+ save_dpi , save_dpi_hint ,
11131192 widgets .HTML ('<hr style="margin:4px 0">' ),
11141193 widgets .HTML ('<b>Reproducible Code</b>' ),
11151194 widgets .HBox ([generate_btn , undo_btn , reset_btn ]),
0 commit comments