From e60c25c242060d692efc2cb696d7f5fa86565b56 Mon Sep 17 00:00:00 2001 From: Matt Newville Date: Sun, 15 Feb 2026 22:31:37 -0600 Subject: [PATCH 01/12] require wxutils > 0.3.7, and darkdetect events for macOS --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b3c1ca2..905af84 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,8 +44,9 @@ classifiers = [ dependencies = [ "wxPython>=4.2.0", - "wxutils>=0.3.3", + "wxutils>=0.3.7", "darkdetect", + "pyobjc-framework-Cocoa; platform_system == 'Darwin'", "matplotlib>=3.9.0", "pytz", "numpy>=1.26", From ff1589d7ef4006fab99c8662d5da0d2878b8879e Mon Sep 17 00:00:00 2001 From: Matt Newville Date: Sun, 15 Feb 2026 22:32:33 -0600 Subject: [PATCH 02/12] dark-theme enabling with (yet unrelased) wxutils 0.4.0 --- wxmplot/colors.py | 401 ++-------------------------------------------- 1 file changed, 13 insertions(+), 388 deletions(-) diff --git a/wxmplot/colors.py b/wxmplot/colors.py index 16ac6fc..55d2501 100644 --- a/wxmplot/colors.py +++ b/wxmplot/colors.py @@ -9,342 +9,9 @@ #from matplotlib.cm import register_cmap from matplotlib import colormaps -DARK_THEME = False -try: - import darkdetect - DARK_THEME = darkdetect.isDark() -except ImportError: - pass - - -COLORS = {'text': wx.Colour(0, 0, 0), - 'text_bg': wx.Colour(255, 255, 255), - 'text_invalid': wx.Colour(240, 0, 10), - 'text_invalid_bg': wx.Colour(253, 253, 90), - 'bg': wx.Colour(240,240,230), - 'hyperlink': wx.Colour(0, 0, 60), - 'nb_active': wx.Colour(254,254,195), - 'nb_area': wx.Colour(250,250,245), - 'nb_text': wx.Colour(10,10,180), - 'nb_activetext': wx.Colour(80,10,10), - 'title': wx.Colour(80,10,10), - 'title_red': wx.Colour(120, 10, 10), - 'title_blue': wx.Colour(10, 10, 120), - 'pvname': wx.Colour(10,10,80), - 'list_bg': wx.Colour(255, 255, 250), - 'list_fg': wx.Colour(5, 5, 25), - 'hline': wx.Colour(80, 80, 200), - 'button_bg': wx.Colour(252, 252, 245), - 'pt_frame_bg': wx.Colour(253, 253, 250), - 'pt_fg': wx.Colour( 20, 20, 120), - 'pt_bg': wx.Colour(253, 253, 250), - 'pt_fgsel': wx.Colour(200, 0, 0), - 'pt_bgsel': wx.Colour(250, 250, 200), - } - -if DARK_THEME: - COLORS = {'text': wx.Colour(255, 255, 255), - 'text_bg': wx.Colour(25, 25, 25), - 'text_invalid': wx.Colour(240, 0, 10), - 'text_invalid_bg': wx.Colour(220, 220, 60), - 'bg': wx.Colour(20, 20, 20), - 'hyperlink': wx.Colour(200, 200, 255), - 'nb_active': wx.Colour(220, 220, 100), - 'nb_area': wx.Colour(60, 60, 80), - 'nb_text': wx.Colour(220,240,245), - 'nb_activetext': wx.Colour(80, 80, 230), - 'title': wx.Colour(240,120,120), - 'title_red': wx.Colour(240,120,120), - 'title_blue': wx.Colour(120,120,250), - 'pvname': wx.Colour(10,10,80), - 'list_bg': wx.Colour(25, 25, 25), - 'list_fg': wx.Colour(5, 5, 125), - 'hline': wx.Colour(220, 220, 250), - 'button_bg': wx.Colour(220, 220, 100), - 'pt_frame_bg': wx.Colour(10, 10, 10), - 'pt_fg': wx.Colour(180, 200, 250), - 'pt_bg': wx.Colour(10, 10, 10), - 'pt_fgsel': wx.Colour(250, 180, 200), - 'pt_bgsel': wx.Colour(30, 20, 80), - } - - -x11_colors = {'aliceblue': (240,248,255), 'antiquewhite': (250,235,215), - 'antiquewhite1': (255,239,219), 'antiquewhite2': (238,223,204), - 'antiquewhite3': (205,192,176), 'antiquewhite4': (139,131,120), - 'aquamarine': (127,255,212), 'aquamarine1': (127,255,212), - 'aquamarine2': (118,238,198), 'aquamarine3': (102,205,170), - 'aquamarine4': ( 69,139,116), 'azure': (240,255,255), - 'azure1': (240,255,255), 'azure2': (224,238,238), - 'azure3': (193,205,205), 'azure4': (131,139,139), - 'beige': (245,245,220), 'bisque': (255,228,196), - 'bisque1': (255,228,196), 'bisque2': (238,213,183), - 'bisque3': (205,183,158), 'bisque4': (139,125,107), - 'black': ( 0, 0, 0), 'blanchedalmond': (255,235,205), - 'blue': ( 0, 0,255), 'blue1': ( 0, 0,255), - 'blue2': ( 0, 0,238), 'blue3': ( 0, 0,205), - 'blue4': ( 0, 0,139), 'blueviolet': (138, 43,226), - 'brown': (165, 42, 42), 'brown1': (255, 64, 64), - 'brown2': (238, 59, 59), 'brown3': (205, 51, 51), - 'brown4': (139, 35, 35), 'burlywood': (222,184,135), - 'burlywood1': (255,211,155), 'burlywood2': (238,197,145), - 'burlywood3': (205,170,125), 'burlywood4': (139,115, 85), - 'cadetblue': ( 95,158,160), 'cadetblue1': (152,245,255), - 'cadetblue2': (142,229,238), 'cadetblue3': (122,197,205), - 'cadetblue4': ( 83,134,139), 'chartreuse': (127,255, 0), - 'chartreuse1': (127,255, 0), 'chartreuse2': (118,238, 0), - 'chartreuse3': (102,205, 0), 'chartreuse4': ( 69,139, 0), - 'chocolate': (210,105, 30), 'chocolate1': (255,127, 36), - 'chocolate2': (238,118, 33), 'chocolate3': (205,102, 29), - 'chocolate4': (139, 69, 19), 'coral': (255,127, 80), - 'coral1': (255,114, 86), 'coral2': (238,106, 80), - 'coral3': (205, 91, 69), 'coral4': (139, 62, 47), - 'cornflowerblue': (100,149,237), 'cornsilk': (255,248,220), - 'cornsilk1': (255,248,220), 'cornsilk2': (238,232,205), - 'cornsilk3': (205,200,177), 'cornsilk4': (139,136,120), - 'cyan': ( 0,255,255), 'cyan1': ( 0,255,255), - 'cyan2': ( 0,238,238), 'cyan3': ( 0,205,205), - 'cyan4': ( 0,139,139), 'darkblue': ( 0, 0,139), - 'darkcyan': ( 0,139,139), 'darkgoldenrod': (184,134, 11), - 'darkgoldenrod1': (255,185, 15), 'darkgoldenrod2': (238,173, 14), - 'darkgoldenrod3': (205,149, 12), 'darkgoldenrod4': (139,101, 8), - 'darkgreen': ( 0,100, 0), 'darkgrey': (169,169,169), - 'darkkhaki': (189,183,107), 'darkmagenta': (139, 0,139), - 'darkolivegreen': ( 85,107, 47), 'darkolivegreen1': (202,255,112), - 'darkolivegreen2': (188,238,104), 'darkolivegreen3': (162,205, 90), - 'darkolivegreen4': (110,139, 61), 'darkorange': (255,140, 0), - 'darkorange1': (255,127, 0), 'darkorange2': (238,118, 0), - 'darkorange3': (205,102, 0), 'darkorange4': (139, 69, 0), - 'darkorchid': (153, 50,204), 'darkorchid1': (191, 62,255), - 'darkorchid2': (178, 58,238), 'darkorchid3': (154, 50,205), - 'darkorchid4': (104, 34,139), 'darkred': (139, 0, 0), - 'darksalmon': (233,150,122), 'darkseagreen': (143,188,143), - 'darkseagreen1': (193,255,193), 'darkseagreen2': (180,238,180), - 'darkseagreen3': (155,205,155), 'darkseagreen4': (105,139,105), - 'darkslateblue': ( 72, 61,139), 'darkslategrey': ( 47, 79, 79), - 'darkslategrey1': (151,255,255), 'darkslategrey2': (141,238,238), - 'darkslategrey3': (121,205,205), 'darkslategrey4': ( 82,139,139), - 'darkturquoise': ( 0,206,209), 'darkviolet': (148, 0,211), - 'deeppink': (255, 20,147), 'deeppink1': (255, 20,147), - 'deeppink2': (238, 18,137), 'deeppink3': (205, 16,118), - 'deeppink4': (139, 10, 80), 'deepskyblue': ( 0,191,255), - 'deepskyblue1': ( 0,191,255), 'deepskyblue2': ( 0,178,238), - 'deepskyblue3': ( 0,154,205), 'deepskyblue4': ( 0,104,139), - 'dimgrey': (105,105,105), 'dodgerblue': ( 30,144,255), - 'dodgerblue1': ( 30,144,255), 'dodgerblue2': ( 28,134,238), - 'dodgerblue3': ( 24,116,205), 'dodgerblue4': ( 16, 78,139), - 'firebrick': (178, 34, 34), 'firebrick1': (255, 48, 48), - 'firebrick2': (238, 44, 44), 'firebrick3': (205, 38, 38), - 'firebrick4': (139, 26, 26), 'floralwhite': (255,250,240), - 'forestgreen': ( 34,139, 34), 'gainsboro': (220,220,220), - 'ghostwhite': (248,248,255), 'gold': (255,215, 0), - 'gold1': (255,215, 0), 'gold2': (238,201, 0), - 'gold3': (205,173, 0), 'gold4': (139,117, 0), - 'goldenrod': (218,165, 32), 'goldenrod1': (255,193, 37), - 'goldenrod2': (238,180, 34), 'goldenrod3': (205,155, 29), - 'goldenrod4': (139,105, 20), 'green': ( 0,255, 0), - 'green1': ( 0,255, 0), 'green2': ( 0,238, 0), - 'green3': ( 0,205, 0), 'green4': ( 0,139, 0), - 'greenyellow': (173,255, 47), 'grey': (190,190,190), - 'grey0': ( 0, 0, 0), 'grey1': ( 3, 3, 3), - 'grey10': ( 26, 26, 26), 'grey100': (255,255,255), - 'grey11': ( 28, 28, 28), 'grey12': ( 31, 31, 31), - 'grey13': ( 33, 33, 33), 'grey14': ( 36, 36, 36), - 'grey15': ( 38, 38, 38), 'grey16': ( 41, 41, 41), - 'grey17': ( 43, 43, 43), 'grey18': ( 46, 46, 46), - 'grey19': ( 48, 48, 48), 'grey2': ( 5, 5, 5), - 'grey20': ( 51, 51, 51), 'grey21': ( 54, 54, 54), - 'grey22': ( 56, 56, 56), 'grey23': ( 59, 59, 59), - 'grey24': ( 61, 61, 61), 'grey25': ( 64, 64, 64), - 'grey26': ( 66, 66, 66), 'grey27': ( 69, 69, 69), - 'grey28': ( 71, 71, 71), 'grey29': ( 74, 74, 74), - 'grey3': ( 8, 8, 8), 'grey30': ( 77, 77, 77), - 'grey31': ( 79, 79, 79), 'grey32': ( 82, 82, 82), - 'grey33': ( 84, 84, 84), 'grey34': ( 87, 87, 87), - 'grey35': ( 89, 89, 89), 'grey36': ( 92, 92, 92), - 'grey37': ( 94, 94, 94), 'grey38': ( 97, 97, 97), - 'grey39': ( 99, 99, 99), 'grey4': ( 10, 10, 10), - 'grey40': (102,102,102), 'grey41': (105,105,105), - 'grey42': (107,107,107), 'grey43': (110,110,110), - 'grey44': (112,112,112), 'grey45': (115,115,115), - 'grey46': (117,117,117), 'grey47': (120,120,120), - 'grey48': (122,122,122), 'grey49': (125,125,125), - 'grey5': ( 13, 13, 13), 'grey50': (127,127,127), - 'grey51': (130,130,130), 'grey52': (133,133,133), - 'grey53': (135,135,135), 'grey54': (138,138,138), - 'grey55': (140,140,140), 'grey56': (143,143,143), - 'grey57': (145,145,145), 'grey58': (148,148,148), - 'grey59': (150,150,150), 'grey6': ( 15, 15, 15), - 'grey60': (153,153,153), 'grey61': (156,156,156), - 'grey62': (158,158,158), 'grey63': (161,161,161), - 'grey64': (163,163,163), 'grey65': (166,166,166), - 'grey66': (168,168,168), 'grey67': (171,171,171), - 'grey68': (173,173,173), 'grey69': (176,176,176), - 'grey7': ( 18, 18, 18), 'grey70': (179,179,179), - 'grey71': (181,181,181), 'grey72': (184,184,184), - 'grey73': (186,186,186), 'grey74': (189,189,189), - 'grey75': (191,191,191), 'grey76': (194,194,194), - 'grey77': (196,196,196), 'grey78': (199,199,199), - 'grey79': (201,201,201), 'grey8': ( 20, 20, 20), - 'grey80': (204,204,204), 'grey81': (207,207,207), - 'grey82': (209,209,209), 'grey83': (212,212,212), - 'grey84': (214,214,214), 'grey85': (217,217,217), - 'grey86': (219,219,219), 'grey87': (222,222,222), - 'grey88': (224,224,224), 'grey89': (227,227,227), - 'grey9': ( 23, 23, 23), 'grey90': (229,229,229), - 'grey91': (232,232,232), 'grey92': (235,235,235), - 'grey93': (237,237,237), 'grey94': (240,240,240), - 'grey95': (242,242,242), 'grey96': (245,245,245), - 'grey97': (247,247,247), 'grey98': (250,250,250), - 'grey99': (252,252,252), 'honeydew': (240,255,240), - 'honeydew1': (240,255,240), 'honeydew2': (224,238,224), - 'honeydew3': (193,205,193), 'honeydew4': (131,139,131), - 'hotpink': (255,105,180), 'hotpink1': (255,110,180), - 'hotpink2': (238,106,167), 'hotpink3': (205, 96,144), - 'hotpink4': (139, 58, 98), 'indianred': (205, 92, 92), - 'indianred1': (255,106,106), 'indianred2': (238, 99, 99), - 'indianred3': (205, 85, 85), 'indianred4': (139, 58, 58), - 'ivory': (255,255,240), 'ivory1': (255,255,240), - 'ivory2': (238,238,224), 'ivory3': (205,205,193), - 'ivory4': (139,139,131), 'khaki': (240,230,140), - 'khaki1': (255,246,143), 'khaki2': (238,230,133), - 'khaki3': (205,198,115), 'khaki4': (139,134, 78), - 'lavender': (230,230,250), 'lavenderblush': (255,240,245), - 'lavenderblush1': (255,240,245), 'lavenderblush2': (238,224,229), - 'lavenderblush3': (205,193,197), 'lavenderblush4': (139,131,134), - 'lawngreen': (124,252, 0), 'lemonchiffon': (255,250,205), - 'lemonchiffon1': (255,250,205), 'lemonchiffon2': (238,233,191), - 'lemonchiffon3': (205,201,165), 'lemonchiffon4': (139,137,112), - 'lightblue': (173,216,230), 'lightblue1': (191,239,255), - 'lightblue2': (178,223,238), 'lightblue3': (154,192,205), - 'lightblue4': (104,131,139), 'lightcoral': (240,128,128), - 'lightcyan': (224,255,255), 'lightcyan1': (224,255,255), - 'lightcyan2': (209,238,238), 'lightcyan3': (180,205,205), - 'lightcyan4': (122,139,139), 'lightgoldenrod': (238,221,130), - 'lightgoldenrod1': (255,236,139), 'lightgoldenrod2': (238,220,130), - 'lightgoldenrod3': (205,190,112), 'lightgoldenrod4': (139,129, 76), - 'lightgoldenrodyellow': (250,250,210), 'lightgreen': (144,238,144), - 'lightgrey': (211,211,211), 'lightpink': (255,182,193), - 'lightpink1': (255,174,185), 'lightpink2': (238,162,173), - 'lightpink3': (205,140,149), 'lightpink4': (139, 95,101), - 'lightsalmon': (255,160,122), 'lightsalmon1': (255,160,122), - 'lightsalmon2': (238,149,114), 'lightsalmon3': (205,129, 98), - 'lightsalmon4': (139, 87, 66), 'lightseagreen': ( 32,178,170), - 'lightskyblue': (135,206,250), 'lightskyblue1': (176,226,255), - 'lightskyblue2': (164,211,238), 'lightskyblue3': (141,182,205), - 'lightskyblue4': ( 96,123,139), 'lightslateblue': (132,112,255), - 'lightslategrey': (119,136,153), 'lightsteelblue': (176,196,222), - 'lightsteelblue1': (202,225,255), 'lightsteelblue2': (188,210,238), - 'lightsteelblue3': (162,181,205), 'lightsteelblue4': (110,123,139), - 'lightyellow': (255,255,224), 'lightyellow1': (255,255,224), - 'lightyellow2': (238,238,209), 'lightyellow3': (205,205,180), - 'lightyellow4': (139,139,122), 'limegreen': ( 50,205, 50), - 'linen': (250,240,230), 'magenta': (255, 0,255), - 'magenta1': (255, 0,255), 'magenta2': (238, 0,238), - 'magenta3': (205, 0,205), 'magenta4': (139, 0,139), - 'maroon': (176, 48, 96), 'maroon1': (255, 52,179), - 'maroon2': (238, 48,167), 'maroon3': (205, 41,144), - 'maroon4': (139, 28, 98), 'mediumaquamarine': (102,205,170), - 'mediumblue': ( 0, 0,205), 'mediumorchid': (186, 85,211), - 'mediumorchid1': (224,102,255), 'mediumorchid2': (209, 95,238), - 'mediumorchid3': (180, 82,205), 'mediumorchid4': (122, 55,139), - 'mediumpurple': (147,112,219), 'mediumpurple1': (171,130,255), - 'mediumpurple2': (159,121,238), 'mediumpurple3': (137,104,205), - 'mediumpurple4': ( 93, 71,139), 'mediumseagreen': ( 60,179,113), - 'mediumslateblue': (123,104,238), 'mediumspringgreen': ( 0,250,154), - 'mediumturquoise': ( 72,209,204), 'mediumvioletred': (199, 21,133), - 'midnightblue': ( 25, 25,112), 'mintcream': (245,255,250), - 'mistyrose': (255,228,225), 'mistyrose1': (255,228,225), - 'mistyrose2': (238,213,210), 'mistyrose3': (205,183,181), - 'mistyrose4': (139,125,123), 'moccasin': (255,228,181), - 'navajowhite': (255,222,173), 'navajowhite1': (255,222,173), - 'navajowhite2': (238,207,161), 'navajowhite3': (205,179,139), - 'navajowhite4': (139,121, 94), 'navy': ( 0, 0,128), - 'navyblue': ( 0, 0,128), 'oldlace': (253,245,230), - 'olivedrab': (107,142, 35), 'olivedrab1': (192,255, 62), - 'olivedrab2': (179,238, 58), 'olivedrab3': (154,205, 50), - 'olivedrab4': (105,139, 34), 'orange': (255,165, 0), - 'orange1': (255,165, 0), 'orange2': (238,154, 0), - 'orange3': (205,133, 0), 'orange4': (139, 90, 0), - 'orangered': (255, 69, 0), 'orangered1': (255, 69, 0), - 'orangered2': (238, 64, 0), 'orangered3': (205, 55, 0), - 'orangered4': (139, 37, 0), 'orchid': (218,112,214), - 'orchid1': (255,131,250), 'orchid2': (238,122,233), - 'orchid3': (205,105,201), 'orchid4': (139, 71,137), - 'palegoldenrod': (238,232,170), 'palegreen': (152,251,152), - 'palegreen1': (154,255,154), 'palegreen2': (144,238,144), - 'palegreen3': (124,205,124), 'palegreen4': ( 84,139, 84), - 'paleturquoise': (175,238,238), 'paleturquoise1': (187,255,255), - 'paleturquoise2': (174,238,238), 'paleturquoise3': (150,205,205), - 'paleturquoise4': (102,139,139), 'palevioletred': (219,112,147), - 'palevioletred1': (255,130,171), 'palevioletred2': (238,121,159), - 'palevioletred3': (205,104,137), 'palevioletred4': (139, 71, 93), - 'papayawhip': (255,239,213), 'peachpuff': (255,218,185), - 'peachpuff1': (255,218,185), 'peachpuff2': (238,203,173), - 'peachpuff3': (205,175,149), 'peachpuff4': (139,119,101), - 'peru': (205,133, 63), 'pink': (255,192,203), - 'pink1': (255,181,197), 'pink2': (238,169,184), - 'pink3': (205,145,158), 'pink4': (139, 99,108), - 'plum': (221,160,221), 'plum1': (255,187,255), - 'plum2': (238,174,238), 'plum3': (205,150,205), - 'plum4': (139,102,139), 'powderblue': (176,224,230), - 'purple': (160, 32,240), 'purple1': (155, 48,255), - 'purple2': (145, 44,238), 'purple3': (125, 38,205), - 'purple4': ( 85, 26,139), 'red': (255, 0, 0), - 'red1': (255, 0, 0), 'red2': (238, 0, 0), - 'red3': (205, 0, 0), 'red4': (139, 0, 0), - 'rosybrown': (188,143,143), 'rosybrown1': (255,193,193), - 'rosybrown2': (238,180,180), 'rosybrown3': (205,155,155), - 'rosybrown4': (139,105,105), 'royalblue': ( 65,105,225), - 'royalblue1': ( 72,118,255), 'royalblue2': ( 67,110,238), - 'royalblue3': ( 58, 95,205), 'royalblue4': ( 39, 64,139), - 'saddlebrown': (139, 69, 19), 'salmon': (250,128,114), - 'salmon1': (255,140,105), 'salmon2': (238,130, 98), - 'salmon3': (205,112, 84), 'salmon4': (139, 76, 57), - 'sandybrown': (244,164, 96), 'seagreen': ( 46,139, 87), - 'seagreen1': ( 84,255,159), 'seagreen2': ( 78,238,148), - 'seagreen3': ( 67,205,128), 'seagreen4': ( 46,139, 87), - 'seashell': (255,245,238), 'seashell1': (255,245,238), - 'seashell2': (238,229,222), 'seashell3': (205,197,191), - 'seashell4': (139,134,130), 'sienna': (160, 82, 45), - 'sienna1': (255,130, 71), 'sienna2': (238,121, 66), - 'sienna3': (205,104, 57), 'sienna4': (139, 71, 38), - 'skyblue': (135,206,235), 'skyblue1': (135,206,255), - 'skyblue2': (126,192,238), 'skyblue3': (108,166,205), - 'skyblue4': ( 74,112,139), 'slateblue': (106, 90,205), - 'slateblue1': (131,111,255), 'slateblue2': (122,103,238), - 'slateblue3': (105, 89,205), 'slateblue4': ( 71, 60,139), - 'slategrey': (112,128,144), 'slategrey1': (198,226,255), - 'slategrey2': (185,211,238), 'slategrey3': (159,182,205), - 'slategrey4': (108,123,139), 'snow': (255,250,250), - 'snow1': (255,250,250), 'snow2': (238,233,233), - 'snow3': (205,201,201), 'snow4': (139,137,137), - 'springgreen': ( 0,255,127), 'springgreen1': ( 0,255,127), - 'springgreen2': ( 0,238,118), 'springgreen3': ( 0,205,102), - 'springgreen4': ( 0,139, 69), 'steelblue': ( 70,130,180), - 'steelblue1': ( 99,184,255), 'steelblue2': ( 92,172,238), - 'steelblue3': ( 79,148,205), 'steelblue4': ( 54,100,139), - 'tan': (210,180,140), 'tan1': (255,165, 79), - 'tan2': (238,154, 73), 'tan3': (205,133, 63), - 'tan4': (139, 90, 43), 'thistle': (216,191,216), - 'thistle1': (255,225,255), 'thistle2': (238,210,238), - 'thistle3': (205,181,205), 'thistle4': (139,123,139), - 'tomato': (255, 99, 71), 'tomato1': (255, 99, 71), - 'tomato2': (238, 92, 66), 'tomato3': (205, 79, 57), - 'tomato4': (139, 54, 38), 'turquoise': ( 64,224,208), - 'turquoise1': ( 0,245,255), 'turquoise2': ( 0,229,238), - 'turquoise3': ( 0,197,205), 'turquoise4': ( 0,134,139), - 'violet': (238,130,238), 'violetred': (208, 32,144), - 'violetred1': (255, 62,150), 'violetred2': (238, 58,140), - 'violetred3': (205, 50,120), 'violetred4': (139, 34, 82), - 'wheat': (245,222,179), 'wheat1': (255,231,186), - 'wheat2': (238,216,174), 'wheat3': (205,186,150), - 'wheat4': (139,126,102), 'white': (255,255,255), - 'whitesmoke': (245,245,245), 'yellow': (255,255, 0), - 'yellow1': (255,255, 0), 'yellow2': (238,238, 0), - 'yellow3': (205,205, 0), 'yellow4': (139,139, 0), - 'yellowgreen': (154,205, 50) - } +from wxutils.colors import (COLORS, GUI_COLORS, GUIColors, X11_COLORS, + set_color, DARK_THEME, use_darkdetect, + register_darkdetect, get_color) def rgb(color, default=(0,0,0)): """ return rgb tuple for named color in rgb.txt or a hex color """ @@ -354,16 +21,13 @@ def rgb(color, default=(0,0,0)): r,g,b = [int(n, 16) for n in (r, g, b)] return (r,g,b) - if c.find(' ')>-1: - c = c.replace(' ','') - if c.find('gray')>-1: - c = c.replace('gray','grey') - if c in x11_colors.keys(): - return x11_colors[c] + c = c.replace(' ', '').replace('gray', 'grey') + if c in X11_COLORS: + return X11_COLORS[c] return default -def hex2rgb(hex): - c = hex +def hex2rgb(hexval): + c = hexval if c[0:1] == '#' and len(c)==7: r,g,b = c[1:3], c[3:5], c[5:] r,g,b = [int(n, 16) for n in (r, g, b)] @@ -383,7 +47,7 @@ def rgb2hex(rgb): def hexcolor(color): - " returns hex color given a tuple, wx.Color, or X11 named color" + " returns hex color given a tuple, wx.Color, or X11 color" # first, if this is a hex color already, return! # Python 3: needs rewrite for str/unicode change if isinstance(color, str): @@ -397,13 +61,10 @@ def hexcolor(color): elif isinstance(color, list): cvals = tuple(color) elif isinstance(color, str): - c = color.lower() - if c.find(' ')>-1: - c = c.replace(' ','') - if c.find('gray')>-1: - c = c.replace('gray','grey') - if c in x11_colors: - cvals = x11_colors[c] + c = color.lower().replace(' ', '').replace('gray', 'grey') + if c in X11_COLORS: + cvals = X11_COLORS[c] + else: try: cvals = color.Red(), color.Green(), color.Blue() @@ -505,39 +166,3 @@ def wxcol2hex(col): def mpl2hexcolor(c): return hexcolor(mpl_color(c)) - - - -# attribitue interface -class GUIColors(object): - def __init__(self): - for key, val in COLORS.items(): - self.add_color(key, val) - - def add_color(self, name, value): - """add_color by name with a value that can be - - a wx.Colour - - a string of the form '#rrggbb', '#rrggbbaa' - - a supported X11 color name - - a tuple or list of (r, g, b), or (r,g,b,a) values - """ - cval = None - if isinstance(value, str): - if value[0] == '#' and len(value) in (7, 9): - r, g, b = value[1:3], value[3:5], value[5:7] - r, g, b = [int(n, 16) for n in (r, g, b)] - a = int(value[7:9], 16) if len(value) == 9 else 255 - cval = wx.Colour(r, g, b, a) - elif value in x11_colors: - cval = wx.Colour(*x11_colors[value]) - if isinstance(value, (tuple, list)) and len(value) in (3, 4): - cval = wx.Colour(*value) - - if isinstance(value, wx.Colour): - cval = value - if cval is not None: - setattr(self, name, cval) - else: - raise ValueError(f"unknown color value {value}") - -GUI_COLORS = GUIColors() From a4ff21fcd007fd15f4058515225551997025545d Mon Sep 17 00:00:00 2001 From: Matt Newville Date: Sun, 15 Feb 2026 22:33:42 -0600 Subject: [PATCH 03/12] dark-theme enabling with (yet unrelased) wxutils 0.4.0 --- wxmplot/config.py | 36 +++++++++++++++-- wxmplot/interactive.py | 3 ++ wxmplot/plotconfigframe.py | 83 ++++++++++++++++++++------------------ 3 files changed, 78 insertions(+), 44 deletions(-) diff --git a/wxmplot/config.py b/wxmplot/config.py index 40f03ad..9510a11 100644 --- a/wxmplot/config.py +++ b/wxmplot/config.py @@ -30,7 +30,7 @@ from matplotlib import rc_params, rcParams import matplotlib.style from cycler import cycler -from .colors import hexcolor, mpl2hexcolor, DARK_THEME +from .colors import hexcolor, mpl2hexcolor, DARK_THEME, use_darkdetect SIDE_YAXES = {'left': 1, 'right': 2, 'right2': 3, 'right3': 4} @@ -62,6 +62,7 @@ linecolors = ('#1f77b4', '#d62728', '#2ca02c', '#ff7f0e', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf') + CONFIG_SAVE = ('auto_margins', 'axes_style', 'current_theme', 'facecolor', 'framecolor', 'gridcolor', 'hidewith_legend', 'labelfont', 'legend_loc', @@ -71,7 +72,12 @@ 'scatter_selectedge', 'scatter_size', 'show_grid', 'show_legend', 'show_legend_frame', 'textcolor', 'titlefont', 'traces', 'viewpad', 'xscale', 'yscale', - 'y2scale', 'y3scale', 'y4scale', 'zoom_style') + 'y2scale', 'y3scale', 'y4scale', 'zoom_style', + 'hist_bins', 'hist_density', 'hist_cumulative', + 'hist_histtype', 'hist_orientation', 'hist_align', + 'hist_stacked', 'hist_rwidth', 'bar_width', + 'bar_bottom', 'bar_align', 'bar_orientation') + light_theme = {'axes.grid': True, 'axes.axisbelow': True, @@ -203,7 +209,13 @@ 'viewpad': 2.5, 'with_data_process': True, 'zoom_style': 'both x and y', - 'labelfont': 9, 'legendfont': 7, 'titlefont': 10} + 'labelfont': 9, 'legendfont': 7, 'titlefont': 10, + 'hist_bins': 10, 'hist_density': False, + 'hist_cumulative': False, 'hist_histtype': 'bar', + 'hist_orientation': 'vertical', 'hist_align': 'mid', + 'hist_stacked': False, 'hist_rwidth': None, + 'bar_width': 0.8, 'bar_bottom': 0, + 'bar_align': 'center', 'bar_orientation': 'vertical'} def ifnot_none(val, default): @@ -277,7 +289,7 @@ class PlotConfig: """Plot Configuration for Line Plots, holding most configuration data """ def __init__(self, canvas=None, panel=None, with_data_process=True, - theme=None, theme_color_callback=None, + theme=None, theme_callback=None, theme_color_callback=None, margin_callback=None, trace_color_callback=None, custom_config=None): @@ -288,6 +300,7 @@ def __init__(self, canvas=None, panel=None, with_data_process=True, self.symbols = list(MarkerMap.keys()) self.trace_color_callback = trace_color_callback self.theme_color_callback = theme_color_callback + self.theme_callback = theme_callback self.margin_callback = margin_callback self.current_theme = theme if self.current_theme is None: @@ -323,6 +336,7 @@ def __init__(self, canvas=None, panel=None, with_data_process=True, self.themes = Themes self.set_defaults() + use_darkdetect() def set_defaults(self): self.zoom_lims = [] @@ -1231,3 +1245,17 @@ def get_viewpads(self): if cur not in o: o.append(cur) return [i/100.0 for i in sorted(o)] + + def make_hist_kwargs(self, trace=0): + """make keywords for axes.hist()""" + kwargs = {} + + print("make hist kwargs") + trace = self.get_trace(trace) + prop = self.traces[trace] + print("L Trace ", prop) + + for key in ('bins', 'density', 'cumulative', 'histtype', + 'orientation', 'align', 'stacked', 'rwidth'): + kwargs[key] = getattr(self, f'hist_{key}') + return kwargs diff --git a/wxmplot/interactive.py b/wxmplot/interactive.py index e6d762b..0a774bd 100644 --- a/wxmplot/interactive.py +++ b/wxmplot/interactive.py @@ -26,6 +26,7 @@ from .imageframe import ImageFrame from .stackedplotframe import StackedPlotFrame from .config import Themes, DARK_THEME +from wxutils import use_darkdetect IMG_DISPLAYS = {} PLOT_DISPLAYS = {} @@ -78,6 +79,8 @@ class wxmplotApp(wx.App, wx.lib.mixins.inspection.InspectionMixin): def __init__(self, with_inspect=False, **kws): self.with_inspect = with_inspect wx.App.__init__(self, **kws) + print("wxmplotApp -- ", use_darkdetect) + use_darkdetect() def OnInit(self): self.createApp() diff --git a/wxmplot/plotconfigframe.py b/wxmplot/plotconfigframe.py index e5ee74a..308ca5a 100644 --- a/wxmplot/plotconfigframe.py +++ b/wxmplot/plotconfigframe.py @@ -7,14 +7,14 @@ import numpy as np import wx import wx.lib.colourselect as csel -import wx.lib.agw.flatnotebook as flat_nb import wx.lib.scrolledpanel as scrolled +from wxutils import (flatnotebook, get_color, set_color, + SimpleText, TextCtrl) + from .utils import LabeledTextCtrl, MenuItem, Choice, FloatSpin from .config import PlotConfig -from .colors import hexcolor, mpl_color, GUI_COLORS - -FNB_STYLE = flat_nb.FNB_NO_X_BUTTON|flat_nb.FNB_SMART_TABS|flat_nb.FNB_NO_NAV_BUTTONS|flat_nb.FNB_NODRAG +from .colors import hexcolor, mpl_color ISPINSIZE = 60 FSPINSIZE = 80 @@ -133,12 +133,8 @@ def DrawPanel(self): font = wx.Font(12, wx.SWISS, wx.NORMAL,wx.NORMAL,False) panel.SetFont(font) - self.nb = flat_nb.FlatNotebook(panel, wx.ID_ANY, agwStyle=FNB_STYLE) - - self.nb.SetTabAreaColour(GUI_COLORS.nb_area) - self.nb.SetActiveTabColour(GUI_COLORS.nb_active) - self.nb.SetNonActiveTabTextColour(GUI_COLORS.nb_text) - self.nb.SetActiveTabTextColour(GUI_COLORS.nb_activetext) + self.nb = flatnotebook(panel, with_nav_buttons=True, with_smart_tabs=True) + print("PlotConfigFrame ", self.nb) self.nb.AddPage(self.make_linetrace_panel(parent=self.nb, font=font), 'Colors and Line Properties', True) @@ -258,12 +254,12 @@ def make_range_panel(self, parent, font=None): auto_b.SetValue(self.conf.user_limits[laxes] == 4*[None]) except: pass - + self.vpad_val = FloatSpin(panel, value=2.5, min_val=0, max_val=100, increment=0.5, digits=2, size=(FSPINSIZE, -1), action=self.onViewPadEvent) self.wids['viewpad'] = self.vpad_val - + xb0, xb1 = laxes.get_xlim() yb0, yb1 = laxes.get_ylim() if user_lims[0] is not None: @@ -502,9 +498,7 @@ def make_text_panel(self, parent, font=None): sizer = wx.GridBagSizer(2, 2) labstyle= wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL - self.titl = LabeledTextCtrl(panel, self.conf.title.replace('\n', '\\n'), - action = partial(self.onText, item='title'), - labeltext='Title: ', size=(475, -1)) + self.xlab = LabeledTextCtrl(panel, self.conf.xlabel.replace('\n', '\\n'), action = partial(self.onText, item='xlabel'), labeltext='X Label: ', size=(475, -1)) @@ -521,6 +515,10 @@ def make_text_panel(self, parent, font=None): action = partial(self.onText, item='y4label'), labeltext='Y4 Label: ', size=(475, -1)) + self.titl = LabeledTextCtrl(panel, self.conf.title.replace('\n', '\\n'), + action = partial(self.onText, item='title'), + labeltext='Title: ', size=(475, -1)) + self.yax_color = wx.CheckBox(panel,-1, 'Use Trace Color for Y Axes', (-1, -1), (-1, -1)) self.yax_color.Bind(wx.EVT_CHECKBOX, self.onYaxes_tracecolor) self.yax_color.SetValue(self.conf.yaxes_tracecolor) @@ -534,21 +532,22 @@ def make_text_panel(self, parent, font=None): self.y3lab.Enable(len(axes) > 2) self.y4lab.Enable(len(axes) > 3) - sizer.Add(self.titl.label, (0, 0), (1, 1), labstyle) - sizer.Add(self.titl, (0, 1), (1, 6), labstyle) - sizer.Add(self.xlab.label, (1, 0), (1, 1), labstyle) - sizer.Add(self.xlab, (1, 1), (1, 6), labstyle) - sizer.Add(self.ylab.label, (2, 0), (1, 1), labstyle) - sizer.Add(self.ylab, (2, 1), (1, 6), labstyle) - sizer.Add(self.y2lab.label, (3, 0), (1, 1), labstyle) - sizer.Add(self.y2lab, (3, 1), (1, 6), labstyle) - sizer.Add(self.y3lab.label, (4, 0), (1, 1), labstyle) - sizer.Add(self.y3lab, (4, 1), (1, 6), labstyle) - sizer.Add(self.y4lab.label, (5, 0), (1, 1), labstyle) - sizer.Add(self.y4lab, (5, 1), (1, 6), labstyle) - sizer.Add(self.yax_color, (6, 0), (1, 6), labstyle) - - irow = 7 + sizer.Add((5, 5), (0, 0), (1, 1), labstyle) + sizer.Add(self.titl.label, (1, 0), (1, 1), labstyle) + sizer.Add(self.titl, (1, 1), (1, 6), labstyle) + sizer.Add(self.xlab.label, (2, 0), (1, 1), labstyle) + sizer.Add(self.xlab, (2, 1), (1, 6), labstyle) + sizer.Add(self.ylab.label, (3, 0), (1, 1), labstyle) + sizer.Add(self.ylab, (3, 1), (1, 6), labstyle) + sizer.Add(self.y2lab.label, (4, 0), (1, 1), labstyle) + sizer.Add(self.y2lab, (4, 1), (1, 6), labstyle) + sizer.Add(self.y3lab.label, (5, 0), (1, 1), labstyle) + sizer.Add(self.y3lab, (5, 1), (1, 6), labstyle) + sizer.Add(self.y4lab.label, (6, 0), (1, 1), labstyle) + sizer.Add(self.y4lab, (6, 1), (1, 6), labstyle) + sizer.Add(self.yax_color, (7, 0), (1, 6), labstyle) + + irow = 8 t0 = wx.StaticText(panel, -1, 'Text Sizes:', style=labstyle) t1 = wx.StaticText(panel, -1, 'Titles:', style=labstyle) @@ -603,7 +602,7 @@ def make_text_panel(self, parent, font=None): togg_leg.Bind(wx.EVT_CHECKBOX, self.onHideWithLegend) togg_leg.SetValue(self.conf.hidewith_legend) - + leg_ttl = wx.StaticText(panel, -1, 'Legend:', size=(-1, -1), style=labstyle) loc_ttl = wx.StaticText(panel, -1, 'Location:', size=(-1, -1), style=labstyle) leg_loc = wx.Choice(panel, -1, choices=self.conf.legend_locs, size=(150, -1)) @@ -673,14 +672,14 @@ def make_linetrace_panel(self, parent, font=None): show_grid.Bind(wx.EVT_CHECKBOX,self.onShowGrid) show_grid.SetValue(cnf.show_grid) self.wids['show_grid'] = show_grid - + show_leg = wx.CheckBox(panel,-1, 'Show Legend ') show_leg.Bind(wx.EVT_CHECKBOX,partial(self.onShowLegend, item='legend')) show_leg.SetValue(cnf.show_legend) if show_leg not in self.show_legend_cbs: self.show_legend_cbs.append(show_leg) self.wids['show_legend'] = show_leg - + show_box = wx.CheckBox(panel,-1, ' Show Top/Right Axes ') show_box.Bind(wx.EVT_CHECKBOX, self.onShowBox) show_box.SetValue(cnf.axes_style == 'box') @@ -779,12 +778,12 @@ def make_linetrace_panel(self, parent, font=None): action=partial(self.onSymbol,trace=i)) sym.SetStringSelection(dsym) - + msz = FloatSpin(panel, size=(FSPINSIZE, -1), value=dmsz, min_val=0, max_val=30, increment=0.5, digits=1, action=partial(self.onMarkerSize, trace=i)) self.choice_markersizes.append(msz) - + zor = FloatSpin(panel, size=(ISPINSIZE, -1), value=dzord, min_val=-500, max_val=500, increment=1, digits=0, action=partial(self.onZorder, trace=i)) @@ -868,6 +867,8 @@ def onTheme(self, event): except KeyError: pass conf.draw_legend() + if callable(conf.theme_callback): + conf.theme_callback(theme) def onLogScale(self, event): xword, yword = event.GetString().split(' / ') @@ -1094,11 +1095,13 @@ def onText(self, event=None, item='trace', trace=0): try: kws = {item: s} self.conf.relabel(**kws) - wid.SetBackgroundColour(GUI_COLORS.text_bg) - wid.SetForegroundColour(GUI_COLORS.text) - except: # as from latex error!a - wid.SetBackgroundColour(GUI_COLORS.text_invalid_bg) - wid.SetForegroundColour(GUI_COLORS.text_invalid) + wid.SetBackgroundColour(get_color('text_bg')) + wid.SetForegroundColour(get_color('text')) + except: # as from latex error + print("invalid text for item ", item, s) + + wid.SetBackgroundColour(get_color('text_invalid_bg')) + wid.SetForegroundColour(get_color('text_invalid')) elif item == 'trace': try: self.conf.set_trace_label(s, trace=trace) From 01d6a11d78c471502c743c120127064a42c5f499 Mon Sep 17 00:00:00 2001 From: Matt Newville Date: Sun, 15 Feb 2026 22:34:43 -0600 Subject: [PATCH 04/12] more dark-theme enabling with wxutils --- wxmplot/imageconf.py | 31 ++++++----------- wxmplot/imagepanel.py | 4 +-- wxmplot/plotpanel.py | 68 +++++++++++++++++++++++++++++++++++-- wxmplot/stackedplotframe.py | 11 ++++-- 4 files changed, 86 insertions(+), 28 deletions(-) diff --git a/wxmplot/imageconf.py b/wxmplot/imageconf.py index 1849fcc..0c9b4b4 100644 --- a/wxmplot/imageconf.py +++ b/wxmplot/imageconf.py @@ -1,26 +1,24 @@ import wx import wx.lib.colourselect as csel -import wx.lib.agw.flatnotebook as flat_nb -import wx.lib.scrolledpanel as scrolled +import wx.lib.scrolledpanel as sXFplocrolled from math import log10 import numpy as np +import yaml + import matplotlib.cm as cmap from matplotlib.ticker import FuncFormatter -from wxutils import get_cwd -from .colors import register_custom_colormaps, hexcolor, hex2rgb, mpl_color, GUI_COLORS +from wxutils import (get_cwd, LabeledTextCtrl, SimpleText, + Check, Choice, HLine, FloatSpin, MenuItem, + flatnotebook, get_color, set_color, use_darkdetect) + +from .colors import register_custom_colormaps, hexcolor, hex2rgb, mpl_color from .config import ifnot_none from .plotconfigframe import autopack -from .utils import LabeledTextCtrl, SimpleText, Check, Choice, HLine, FloatSpin, MenuItem -try: - import yaml - HAS_YAML = True -except ImportError: - HAS_YAML = False cm_names = register_custom_colormaps() @@ -61,7 +59,6 @@ RGB_COLORS = ('red', 'green', 'blue') labstyle = wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.ALL -FNB_STYLE = flat_nb.FNB_NO_X_BUTTON|flat_nb.FNB_SMART_TABS|flat_nb.FNB_NO_NAV_BUTTONS|flat_nb.FNB_NODRAG class ImageConfig: def __init__(self, axes=None, fig=None, canvas=None): @@ -122,6 +119,7 @@ def __init__(self, axes=None, fig=None, canvas=None): self._xfmt = None self._yfmt = None self.set_formatters() + use_darkdetect() def set_colormap(self, name, reverse=False, icol=0): self.cmap_reverse = reverse @@ -409,8 +407,6 @@ def __init__(self, parent=None, config=None, trace_color_callback=None): self.SetMenuBar(mbar) def save_config(self, evt=None, fname='wxmplot.yaml'): - if not HAS_YAML: - return file_choices = 'YAML Config File (*.yaml)|*.yaml' dlg = wx.FileDialog(self, message='Save image configuration', defaultDir=get_cwd(), @@ -426,8 +422,6 @@ def save_config(self, evt=None, fname='wxmplot.yaml'): def load_config(self, evt=None): - if not HAS_YAML: - return file_choices = 'YAML Config File (*.yaml)|*.yaml' dlg = wx.FileDialog(self, message='Read image configuration', defaultDir=get_cwd(), @@ -704,12 +698,7 @@ def DrawPanel(self): font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, False) self.SetFont(font) - self.nb = flat_nb.FlatNotebook(self, wx.ID_ANY, agwStyle=FNB_STYLE) - self.nb.SetTabAreaColour(GUI_COLORS.nb_area) - self.nb.SetActiveTabColour(GUI_COLORS.nb_active) - self.nb.SetNonActiveTabTextColour(GUI_COLORS.nb_text) - self.nb.SetActiveTabTextColour(GUI_COLORS.nb_activetext) - + self.nb = flatnotebook(self, with_nav_buttons=True, with_smart_tabs=True) self.nb.AddPage(self.make_contour_panel(parent=self.nb, font=font), 'Contours', True) diff --git a/wxmplot/imagepanel.py b/wxmplot/imagepanel.py index 6a71e53..b9d311a 100644 --- a/wxmplot/imagepanel.py +++ b/wxmplot/imagepanel.py @@ -19,7 +19,7 @@ from .basepanel import BasePanel from .utils import inside_poly, MenuItem from .plotframe import PlotFrame -from .colors import GUI_COLORS, wxcol2hex +from .colors import wxcol2hex, get_color class ImagePanel(BasePanel): """ @@ -345,7 +345,7 @@ def BuildPanel(self): self.fig = Figure(figsize, dpi=self.dpi) self.gridspec = GridSpec(1,1) self.axes = self.fig.add_subplot(self.gridspec[0], - facecolor=wxcol2hex(GUI_COLORS.text_bg)) + facecolor=wxcol2hex(get_color('text_bg'))) self.canvas = FigureCanvasWxAgg(self, -1, self.fig) self.canvas.gui_repaint = self.gui_repaint self.conf.axes = self.axes diff --git a/wxmplot/plotpanel.py b/wxmplot/plotpanel.py index 6516410..19722eb 100644 --- a/wxmplot/plotpanel.py +++ b/wxmplot/plotpanel.py @@ -421,6 +421,71 @@ def unpack_tracedata(tdat, **kws): self.draw() # self.canvas.Refresh() + def hist(self, x, bins=None, density=None, cumulative=None, + histtype=None, orientation=None, align=None, + stacked=None, rwidth=None, force_draw=True, title=None, + xlabel=None, ylabel=None, y2label=None, y3label=None, + y4label=None, use_dates=False, dates_style=None, yaxes=1, + side=None, **kws): + """hist""" + if bins is not None: + self.conf.hist_bins = bins + if density is not None: + self.conf.hist_density = density + if cumulative is not None: + self.conf.hist_cumulative = cumulative + if histtype is not None: + self.conf.hist_histtype = histtype + if orientation is not None: + self.conf.hist_orientation = orientation + if align is not None: + self.conf.hist_align = align + if stacked is not None: + self.conf.hist_stacked = stacked + if rwidth is not None: + self.conf.hist_rwidth = rwidth + + allaxes = self.fig.get_axes() + if len(allaxes) > 1: + for ax in allaxes[1:]: + if ax in self.data_range: + self.data_range.pop(ax) + self.fig.delaxes(ax) + + self.data_range = {} + self.conf.zoom_lims = [] + self.conf.axes_hist = {} + self.clear() + yaxes, axes = self.get_yaxes(yaxes, side=side) + + self.conf.yscale = 'linear' + self.conf.yscale = 'linear' + self.conf.y2scale = 'linear' + self.conf.y3scale = 'linear' + self.conf.y4scale = 'linear' + self.conf.user_limits[axes] = 4*[None] + + if xlabel is not None: + self.set_xlabel(xlabel, delay_draw=True) + if ylabel is not None: + self.set_ylabel(ylabel, delay_draw=True) + if y2label is not None: + self.set_y2label(y2label, delay_draw=True) + if y3label is not None: + self.set_y3label(y3label, delay_draw=True) + if y4label is not None: + self.set_y4label(y4label, delay_draw=True) + if title is not None: + self.set_title(title, delay_draw=True) + self.dates_style = ifnot_none(dates_style, self.dates_style) + self.use_dates = ifnot_none(use_dates, self.use_dates) + + kws = self.conf.make_hist_kwargs() + print("hist : ", kws) + self.conf.axes_hist[0] = axes.hist(x, **kws) + + + def get_zoomlimits(self): return self.axes, self.get_viewlimits(), self.conf.zoom_lims @@ -799,8 +864,7 @@ def configure(self, event=None): self.win_config = None if self.win_config is None: - self.win_config = PlotConfigFrame(parent=self, - config=self.conf, + self.win_config = PlotConfigFrame(parent=self, config=self.conf, trace_color_callback=self.trace_color_callback) self.win_config.Raise() diff --git a/wxmplot/stackedplotframe.py b/wxmplot/stackedplotframe.py index b6ca710..f8145b8 100644 --- a/wxmplot/stackedplotframe.py +++ b/wxmplot/stackedplotframe.py @@ -19,7 +19,7 @@ from .plotpanel import PlotPanel from .baseframe import BaseFrame from .utils import gformat -from .colors import GUI_COLORS, wxcol2hex +from .colors import wxcol2hex, get_color class StackedPlotFrame(BaseFrame): """ @@ -231,7 +231,6 @@ def configure(self, event=None, panel='top'): panel = self.get_panel(panel) panel.configure(event=event) - #### ## create GUI #### @@ -259,6 +258,7 @@ def BuildFrame(self): # self.panel_bot.conf.labelfont.set_size(lsize-2) self.panel_bot.yformatter = self.bot_yformatter + self.panel.conf.theme_callback = self.onTheme self.panel.conf.theme_color_callback = self.onThemeColor self.panel.conf.margin_callback = self.onMargins @@ -276,7 +276,7 @@ def BuildFrame(self): pan.set_viewlimits = partial(self.set_viewlimits, panel=pname) pan.unzoom_all = self.unzoom_all pan.unzoom = self.unzoom - pan.canvas.figure.set_facecolor(wxcol2hex(GUI_COLORS.bg)) + pan.canvas.figure.set_facecolor(wxcol2hex(get_color('bg'))) # suppress mouse events on the bottom panel null_events = {'leftdown': None, 'leftup': None, 'rightdown': None, @@ -336,6 +336,11 @@ def toggle_grid(self, event=None, show=None): for p in (self.panel, self.panel_bot): p.conf.enable_grid(show) + def onTheme(self, theme): + """pass theme """ + self.panel_bot.conf.set_theme(theme=theme) + + def onThemeColor(self, color, item): """pass theme colors to bottom panel""" bconf = self.panel_bot.conf From 83e2cbdc64bee1c86f3344db62ce5c924c00b315 Mon Sep 17 00:00:00 2001 From: Matt Newville Date: Sun, 15 Feb 2026 22:38:05 -0600 Subject: [PATCH 05/12] cleanup --- wxmplot/imageconf.py | 2 +- wxmplot/interactive.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/wxmplot/imageconf.py b/wxmplot/imageconf.py index 0c9b4b4..7c6f8ba 100644 --- a/wxmplot/imageconf.py +++ b/wxmplot/imageconf.py @@ -1,6 +1,6 @@ import wx import wx.lib.colourselect as csel -import wx.lib.scrolledpanel as sXFplocrolled +import wx.lib.scrolledpanel as scrolled from math import log10 diff --git a/wxmplot/interactive.py b/wxmplot/interactive.py index 0a774bd..a67bdd3 100644 --- a/wxmplot/interactive.py +++ b/wxmplot/interactive.py @@ -79,7 +79,6 @@ class wxmplotApp(wx.App, wx.lib.mixins.inspection.InspectionMixin): def __init__(self, with_inspect=False, **kws): self.with_inspect = with_inspect wx.App.__init__(self, **kws) - print("wxmplotApp -- ", use_darkdetect) use_darkdetect() def OnInit(self): From 309fbd1c44784d6fde65af19252d1fddc0b01808 Mon Sep 17 00:00:00 2001 From: Matt Newville Date: Wed, 18 Feb 2026 09:56:52 -0600 Subject: [PATCH 06/12] do not need to register or setup darkdetect listeners here --- wxmplot/colors.py | 3 +-- wxmplot/config.py | 3 +-- wxmplot/imageconf.py | 3 +-- wxmplot/interactive.py | 2 -- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/wxmplot/colors.py b/wxmplot/colors.py index 55d2501..76e06ec 100644 --- a/wxmplot/colors.py +++ b/wxmplot/colors.py @@ -10,8 +10,7 @@ from matplotlib import colormaps from wxutils.colors import (COLORS, GUI_COLORS, GUIColors, X11_COLORS, - set_color, DARK_THEME, use_darkdetect, - register_darkdetect, get_color) + set_color, DARK_THEME, get_color) def rgb(color, default=(0,0,0)): """ return rgb tuple for named color in rgb.txt or a hex color """ diff --git a/wxmplot/config.py b/wxmplot/config.py index 9510a11..cad7de0 100644 --- a/wxmplot/config.py +++ b/wxmplot/config.py @@ -30,7 +30,7 @@ from matplotlib import rc_params, rcParams import matplotlib.style from cycler import cycler -from .colors import hexcolor, mpl2hexcolor, DARK_THEME, use_darkdetect +from .colors import hexcolor, mpl2hexcolor, DARK_THEME SIDE_YAXES = {'left': 1, 'right': 2, 'right2': 3, 'right3': 4} @@ -336,7 +336,6 @@ def __init__(self, canvas=None, panel=None, with_data_process=True, self.themes = Themes self.set_defaults() - use_darkdetect() def set_defaults(self): self.zoom_lims = [] diff --git a/wxmplot/imageconf.py b/wxmplot/imageconf.py index 7c6f8ba..b801dc0 100644 --- a/wxmplot/imageconf.py +++ b/wxmplot/imageconf.py @@ -13,7 +13,7 @@ from wxutils import (get_cwd, LabeledTextCtrl, SimpleText, Check, Choice, HLine, FloatSpin, MenuItem, - flatnotebook, get_color, set_color, use_darkdetect) + flatnotebook, get_color, set_color) from .colors import register_custom_colormaps, hexcolor, hex2rgb, mpl_color from .config import ifnot_none @@ -119,7 +119,6 @@ def __init__(self, axes=None, fig=None, canvas=None): self._xfmt = None self._yfmt = None self.set_formatters() - use_darkdetect() def set_colormap(self, name, reverse=False, icol=0): self.cmap_reverse = reverse diff --git a/wxmplot/interactive.py b/wxmplot/interactive.py index a67bdd3..e6d762b 100644 --- a/wxmplot/interactive.py +++ b/wxmplot/interactive.py @@ -26,7 +26,6 @@ from .imageframe import ImageFrame from .stackedplotframe import StackedPlotFrame from .config import Themes, DARK_THEME -from wxutils import use_darkdetect IMG_DISPLAYS = {} PLOT_DISPLAYS = {} @@ -79,7 +78,6 @@ class wxmplotApp(wx.App, wx.lib.mixins.inspection.InspectionMixin): def __init__(self, with_inspect=False, **kws): self.with_inspect = with_inspect wx.App.__init__(self, **kws) - use_darkdetect() def OnInit(self): self.createApp() From 98535341064e74b978073b03c2f3f4596b9bfe1b Mon Sep 17 00:00:00 2001 From: Matt Newville Date: Sun, 22 Feb 2026 18:17:42 -0600 Subject: [PATCH 07/12] update wxutils dependency, better support for dark mode --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 905af84..097781d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ classifiers = [ dependencies = [ "wxPython>=4.2.0", - "wxutils>=0.3.7", + "wxutils>=2026.1.0", "darkdetect", "pyobjc-framework-Cocoa; platform_system == 'Darwin'", "matplotlib>=3.9.0", From 9c90e946e7e9dcd4f2a33e568df33c4aae7364f0 Mon Sep 17 00:00:00 2001 From: Matt Newville Date: Sun, 22 Feb 2026 19:15:47 -0600 Subject: [PATCH 08/12] add default theme='auto', changing from 'dark' to 'light' based on system dark mode --- wxmplot/config.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/wxmplot/config.py b/wxmplot/config.py index cad7de0..386b6da 100644 --- a/wxmplot/config.py +++ b/wxmplot/config.py @@ -29,8 +29,9 @@ from matplotlib.font_manager import FontProperties from matplotlib import rc_params, rcParams import matplotlib.style +from wxutils.colors import DARK_THEME from cycler import cycler -from .colors import hexcolor, mpl2hexcolor, DARK_THEME +from .colors import hexcolor, mpl2hexcolor SIDE_YAXES = {'left': 1, 'right': 2, 'right2': 3, 'right3': 4} @@ -130,7 +131,7 @@ Themes = {} -for tname in ('light', 'white-background', 'dark', 'matplotlib', +for tname in ('auto', 'light', 'white-background', 'dark', 'matplotlib', 'ggplot', 'bmh', 'fivethirtyeight', 'grayscale', 'dark_background', 'mpl_gallery', 'petroff10', 'tableau-colorblind10', 'Solarize_Light2', 'seaborn', @@ -140,8 +141,6 @@ 'seaborn-pastel', 'seaborn-paper', 'seaborn-poster', 'seaborn-talk', 'seaborn-ticks', 'seaborn-white', 'seaborn-whitegrid'): - - theme = rc_params() theme['backend'] = 'WXAgg' if tname == 'matplotlib': @@ -176,7 +175,7 @@ default_config = {'auto_margins': True, 'axes_style': 'box', - 'current_theme': 'dark' if DARK_THEME else 'light', + 'current_theme': 'auto', #== 'dark' if DARK_THEME else 'light' 'data_deriv': False, 'data_expr': None, 'draggable_legend': False, @@ -289,7 +288,7 @@ class PlotConfig: """Plot Configuration for Line Plots, holding most configuration data """ def __init__(self, canvas=None, panel=None, with_data_process=True, - theme=None, theme_callback=None, theme_color_callback=None, + theme='auto', theme_callback=None, theme_color_callback=None, margin_callback=None, trace_color_callback=None, custom_config=None): @@ -304,7 +303,7 @@ def __init__(self, canvas=None, panel=None, with_data_process=True, self.margin_callback = margin_callback self.current_theme = theme if self.current_theme is None: - self.current_theme = 'dark' if DARK_THEME else 'light' + self.current_theme = 'auto' self.legend_map = {} self.legend_locs = ['best', 'upper right' , 'lower right', 'center right', 'upper left', 'lower left', 'center left', @@ -365,10 +364,15 @@ def set_defaults(self): self.set_theme() - def set_theme(self, theme=None): + def set_theme(self, theme='auto', is_dark=None): if theme in self.themes: self.current_theme = theme + cur_theme = self.themes[self.current_theme] + if theme in ('auto', 'None', '', None): + if is_dark is None: + is_dark = DARK_THEME + cur_theme = self.themes['dark'] if is_dark else self.themes['light'] rcParams.update(cur_theme) self.show_grid = cur_theme['axes.grid'] @@ -1249,10 +1253,10 @@ def make_hist_kwargs(self, trace=0): """make keywords for axes.hist()""" kwargs = {} - print("make hist kwargs") + # print("make hist kwargs") trace = self.get_trace(trace) prop = self.traces[trace] - print("L Trace ", prop) + # print("L Trace ", prop) for key in ('bins', 'density', 'cumulative', 'histtype', 'orientation', 'align', 'stacked', 'rwidth'): From c8fbdb7ee7181e931a50530c73562ba43ec9ae26 Mon Sep 17 00:00:00 2001 From: Matt Newville Date: Sun, 22 Feb 2026 19:16:08 -0600 Subject: [PATCH 09/12] change default theme to 'auto' --- wxmplot/interactive.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wxmplot/interactive.py b/wxmplot/interactive.py index e6d762b..77efeee 100644 --- a/wxmplot/interactive.py +++ b/wxmplot/interactive.py @@ -360,7 +360,7 @@ def get_image_window(win=1, size=None, wintitle=None): return display -def plot(x,y=None, win=1, new=False, size=None, wintitle=None, theme=None, **kws): +def plot(x,y=None, win=1, new=False, size=None, wintitle=None, theme='auto', **kws): """plot(x, y, win=1, new=False, ...) Plot trace of x, y arrays in a PlotFrame From f0b8f3b945dd12baf374149f2ce1e23ac1b5e0d8 Mon Sep 17 00:00:00 2001 From: Matt Newville Date: Sun, 22 Feb 2026 19:16:52 -0600 Subject: [PATCH 10/12] change default theme to 'auto', add darkdetect callback to react to changed dark mode --- wxmplot/plotpanel.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/wxmplot/plotpanel.py b/wxmplot/plotpanel.py index 19722eb..a16dc8a 100644 --- a/wxmplot/plotpanel.py +++ b/wxmplot/plotpanel.py @@ -20,7 +20,7 @@ from matplotlib.gridspec import GridSpec from matplotlib.colors import colorConverter -from wxutils import get_cwd +from wxutils import get_cwd, DARK_THEME, register_darkdetect from .basepanel import BasePanel from .config import PlotConfig, ifnot_none, SIDE_YAXES from .utils import inside_poly, MenuItem, fix_filename @@ -57,7 +57,7 @@ class PlotPanel(BasePanel): def __init__(self, parent, size=(700, 450), dpi=150, axisbg=None, facecolor=None, fontsize=9, trace_color_callback=None, - output_title='plot', with_data_process=True, theme=None, + output_title='plot', with_data_process=True, theme='auto', **kws): self.trace_color_callback = trace_color_callback @@ -87,6 +87,12 @@ def __init__(self, parent, size=(700, 450), dpi=150, axisbg=None, self.conf.axes_traces = {} self.use_dates = False self.dates_style = None + register_darkdetect(self.onDarkMode) + + def onDarkMode(self, is_dark=None): + if self.conf.current_theme in ('auto', 'None', '', None): + self.conf.set_theme('auto', is_dark=is_dark) + wx.CallAfter(self.Refresh) def plot(self, xdata, ydata=None, title=None, xlabel=None, ylabel=None, y2label=None, y3label=None, y4label=None, use_dates=False, @@ -481,7 +487,6 @@ def hist(self, x, bins=None, density=None, cumulative=None, self.use_dates = ifnot_none(use_dates, self.use_dates) kws = self.conf.make_hist_kwargs() - print("hist : ", kws) self.conf.axes_hist[0] = axes.hist(x, **kws) From ea24707a0214a8fb2d8bae64451e92329ee2f223 Mon Sep 17 00:00:00 2001 From: Matt Newville Date: Sun, 22 Feb 2026 19:20:00 -0600 Subject: [PATCH 11/12] document theme='auto' --- doc/plotpanel.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/plotpanel.rst b/doc/plotpanel.rst index ff809df..368776a 100644 --- a/doc/plotpanel.rst +++ b/doc/plotpanel.rst @@ -95,7 +95,7 @@ same meaning, as indicated by the right-most column. +------------------+------------+---------+------------------------------------------------+-----+--------------+ | label | string | None | trace label (defaults to 'trace N') | 1 | yes | +------------------+------------+---------+------------------------------------------------+-----+--------------+ - | theme | str | '' | theme for colors and text size | 2 | no | + | theme | str | 'auto' | theme for colors and text size | 2 | no | +------------------+------------+---------+------------------------------------------------+-----+--------------+ | color | string | blue | color to use for trace | 3 | yes | +------------------+------------+---------+------------------------------------------------+-----+--------------+ @@ -187,12 +187,15 @@ same meaning, as indicated by the right-most column. means to use the previously used value. 2. The *theme* will set the color palette and make stylistic choices. Choices include - 'light' (the default), 'white-background', 'dark', 'matplotlib', 'seaborn', + 'auto' (default), 'light', 'white-background', 'dark', 'matplotlib', 'seaborn', 'ggplot', 'bmh', 'fivethirtyeight', 'grayscale', 'dark_background', 'tableau-colorblind10', 'seaborn-bright', 'seaborn-colorblind', 'seaborn-dark', 'seaborn-darkgrid', 'seaborn-dark-palette', 'seaborn-deep', 'seaborn-notebook', 'seaborn-muted', 'seaborn-pastel', 'seaborn-paper', 'seaborn-poster', - 'seaborn-talk', 'seaborn-ticks', 'seaborn-white', 'seaborn-whitegrid', and + 'seaborn-talk', 'seaborn-ticks', 'seaborn-white', and + 'seaborn-whitegrid. Note that the default 'auto' will choose + between 'light' and 'dark' based on system Dark Mode, and + automatically switch when that Dark Mode changes. 3. All *color* arguments can be a common color name ("blue", "red", "black", etc), a standard X11 color names ("cadetblue3", "darkgreen", etc), or an RGB hex color From 97fb3736c3b7b6f977f9970021373f8a964220db Mon Sep 17 00:00:00 2001 From: Matt Newville Date: Sun, 22 Feb 2026 19:32:02 -0600 Subject: [PATCH 12/12] drop Python3.9, add Python3.14 to GH Actions --- .github/workflows/test_wxmplot_install.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_wxmplot_install.yml b/.github/workflows/test_wxmplot_install.yml index 9acce40..6b02998 100644 --- a/.github/workflows/test_wxmplot_install.yml +++ b/.github/workflows/test_wxmplot_install.yml @@ -14,7 +14,7 @@ jobs: max-parallel: 4 fail-fast: false matrix: - python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] + python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] defaults: run: shell: bash -l {0} @@ -34,7 +34,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install conda dependencies run: | - conda install -y -c conda-forge python=${{ matrix.python-version }} "numpy>=1.23" "matplotlib>=3.8" "wxpython>=4.2" pip pytest + conda install -y -c conda-forge python=${{ matrix.python-version }} "numpy>=1.23" "matplotlib>=3.8" "wxpython>=4.2.2" pip pytest conda info -a conda list - name: Install wxmplot with pip