@@ -157,6 +157,111 @@ def _get_cn_serif_fonts() -> list:
157157 return found
158158
159159
160+ def use_chinese (style : str = 'auto' ) -> str :
161+ """
162+ One-line fix for Chinese text rendering in matplotlib.
163+
164+ Call this **before** creating any plots. Automatically detects
165+ the best Chinese font on your system (macOS, Windows, Linux).
166+
167+ Parameters
168+ ----------
169+ style : str, default 'auto'
170+ - ``'auto'``: auto-detect the best available Chinese font
171+ - ``'serif'``: prefer serif fonts (宋体 Songti SC, SimSun)
172+ - ``'sans'``: prefer sans-serif fonts (苹方 PingFang, 黑体 SimHei)
173+ - Any specific font name, e.g. ``'Songti SC'``, ``'SimHei'``
174+
175+ Returns
176+ -------
177+ str
178+ The font name that was configured.
179+
180+ Examples
181+ --------
182+ >>> import statspai as sp
183+ >>> sp.use_chinese() # auto-detect best font
184+ >>> sp.use_chinese('serif') # prefer 宋体
185+ >>> sp.use_chinese('sans') # prefer 黑体/苹方
186+ >>> sp.use_chinese('Kaiti SC') # use specific font
187+ """
188+ try :
189+ import matplotlib as mpl
190+ from matplotlib .font_manager import fontManager , findfont , FontProperties
191+ except ImportError :
192+ raise ImportError ("matplotlib required. Install: pip install matplotlib" )
193+
194+ available = {f .name for f in fontManager .ttflist }
195+
196+ # Priority lists for each platform
197+ serif_priority = [
198+ 'Songti SC' , 'Noto Serif CJK SC' , 'SimSun' , 'STSong' ,
199+ 'Hiragino Mincho ProN' , 'AR PL UMing CN' ,
200+ ]
201+ sans_priority = [
202+ 'PingFang SC' , 'PingFang HK' , 'Hiragino Sans GB' ,
203+ 'Microsoft YaHei' , 'SimHei' , 'Noto Sans CJK SC' ,
204+ 'Heiti TC' , 'STHeiti' , 'WenQuanYi Micro Hei' ,
205+ 'Hiragino Sans' ,
206+ ]
207+ all_priority = sans_priority + serif_priority + ['Arial Unicode MS' ]
208+
209+ chosen = None
210+
211+ if style == 'auto' :
212+ for font in all_priority :
213+ if font in available :
214+ chosen = font
215+ break
216+ elif style == 'serif' :
217+ for font in serif_priority :
218+ if font in available :
219+ chosen = font
220+ break
221+ elif style == 'sans' :
222+ for font in sans_priority :
223+ if font in available :
224+ chosen = font
225+ break
226+ elif style in available :
227+ chosen = style
228+ else :
229+ # Try as-is, matplotlib will warn if not found
230+ chosen = style
231+
232+ if chosen is None :
233+ import warnings
234+ warnings .warn (
235+ "No Chinese font found on this system. "
236+ "Install one of: Noto CJK, SimSun, PingFang, "
237+ "Microsoft YaHei, or WenQuanYi." ,
238+ UserWarning , stacklevel = 2 ,
239+ )
240+ return ''
241+
242+ # Determine family
243+ _serif_names = ('Song' , 'Serif' , 'Mincho' , 'Ming' , 'STSong' , 'Noto Serif' )
244+ if any (kw in chosen for kw in _serif_names ):
245+ family = 'serif'
246+ mpl .rcParams ['font.family' ] = 'serif'
247+ current = list (mpl .rcParams .get ('font.serif' , []))
248+ if chosen not in current :
249+ current .insert (0 , chosen )
250+ mpl .rcParams ['font.serif' ] = current
251+ else :
252+ family = 'sans-serif'
253+ mpl .rcParams ['font.family' ] = 'sans-serif'
254+ current = list (mpl .rcParams .get ('font.sans-serif' , []))
255+ if chosen not in current :
256+ current .insert (0 , chosen )
257+ mpl .rcParams ['font.sans-serif' ] = current
258+
259+ # Fix minus sign
260+ mpl .rcParams ['axes.unicode_minus' ] = False
261+
262+ return chosen
263+
264+
160265def set_theme (
161266 name : str = 'academic' ,
162267 palette : Optional [str ] = None ,
0 commit comments