From 4ea710f62c959e3840615c9030d7ba03fbc684cf Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 11 Mar 2025 00:18:53 -0500 Subject: [PATCH] scripts: Adopted % modifiers in all attr arguments This turns out to be extremely useful, for the sole purpose of being able to specify colors/formats/etc in csv fields (-C'%(fields)s' for example, or -C'#%(field)06x' for a cooler example). This is a bit tricky for --chars, but doable with a psplit helper function. Also fixed a bug in plot.py where we weren't using dataattrs_ correctly. --- scripts/plot.py | 44 ++++++++++++++++++++++++++++++++------------ scripts/plotmpl.py | 9 +++++---- scripts/treemap.py | 27 ++++++++++++++++++++------- scripts/treemapd3.py | 5 +++-- 4 files changed, 60 insertions(+), 25 deletions(-) diff --git a/scripts/plot.py b/scripts/plot.py index 67d81f96..d9e4230d 100755 --- a/scripts/plot.py +++ b/scripts/plot.py @@ -511,6 +511,17 @@ def punescape(s, attrs=None): else: assert False return re.sub(pattern, unescape, s) +# split %-escaped strings into chars +def psplit(s): + pattern = re.compile( + '%[%n]' + '|' '%x..' + '|' '%u....' + '|' '%U........' + '|' '%\((?P[^)]*)\)' + '(?P[+\- #0-9\.]*[sdboxXfFeEgG])') + return [m.group() for m in re.finditer(pattern.pattern + '|.', s)] + # a hack log that preserves sign, with a linear region between -1 and 1 def symlog(x): @@ -1010,9 +1021,9 @@ def main(csv_paths, *, chars_ = [] for char in chars: if isinstance(char, tuple): - chars_.extend((char[0], c) for c in char[1]) + chars_.extend((char[0], c) for c in psplit(char[1])) else: - chars_.extend(char) + chars_.extend(psplit(char)) chars_ = Attr(chars_, defaults=( CHARS_POINTS_AND_LINES if points_and_lines else [True])) @@ -1020,9 +1031,9 @@ def main(csv_paths, *, line_chars_ = [] for line_char in line_chars: if isinstance(line_char, tuple): - line_chars_.extend((line_char[0], c) for c in line_char[1]) + line_chars_.extend((line_char[0], c) for c in psplit(line_char[1])) else: - line_chars_.extend(line_char) + line_chars_.extend(psplit(line_char)) line_chars_ = Attr(line_chars_, defaults=( [True] if points_and_lines or not points else [False])) @@ -1182,13 +1193,21 @@ def main(csv_paths, *, # figure out colors/chars here so that subplot defines # don't change them later, that'd be bad - datachars_ = {name: chars_[i, name] + datachars_ = {name: (lambda c: + c if isinstance(c, bool) + # limit to 1 char + else punescape(c, dataattrs_[name])[0])( + chars_[i, name]) for i, name in enumerate(datasets_.keys())} - dataline_chars_ = {name: line_chars_[i, name] + dataline_chars_ = {name: (lambda c: + c if isinstance(c, bool) + # limit to 1 char + else punescape(c, dataattrs_[name])[0])( + line_chars_[i, name]) for i, name in enumerate(datasets_.keys())} - datacolors_ = {name: colors_[i, name] + datacolors_ = {name: punescape(colors_[i, name], dataattrs_[name]) for i, name in enumerate(datasets_.keys())} - datalabels_ = {name: punescape(labels_[i, name], mergedattrs_) + datalabels_ = {name: punescape(labels_[i, name], dataattrs_[name]) for i, name in enumerate(datasets_.keys()) if (i, name) in labels_} @@ -1215,7 +1234,7 @@ def main(csv_paths, *, else ','.join(name)) if label: - legend_.append((label, colors_[i, name])) + legend_.append((label, datacolors_[name])) legend_width = max(legend_width, len(label)+1) # figure out our canvas size @@ -1695,7 +1714,7 @@ if __name__ == "__main__": if '=' in x else x.strip(), help="Add characters to use for points. Can be assigned to a " "specific group where a group is the comma-separated " - "'by' fields.") + "'by' fields. Accepts %% modifiers.") parser.add_argument( '-_', '--add-line-char', '--line-chars', dest='line_chars', @@ -1708,7 +1727,7 @@ if __name__ == "__main__": if '=' in x else x.strip(), help="Add characters to use for lines. Can be assigned to a " "specific group where a group is the comma-separated " - "'by' fields.") + "'by' fields. Accepts %% modifiers.") parser.add_argument( '-C', '--add-color', dest='colors', @@ -1720,7 +1739,8 @@ if __name__ == "__main__": )(*x.split('=', 1)) if '=' in x else x.strip(), help="Add a color to use. Can be assigned to a specific group " - "where a group is the comma-separated 'by' fields.") + "where a group is the comma-separated 'by' fields. Accepts %% " + "modifiers.") parser.add_argument( '--color', choices=['never', 'always', 'auto'], diff --git a/scripts/plotmpl.py b/scripts/plotmpl.py index b468c4b1..4dcaf3e8 100755 --- a/scripts/plotmpl.py +++ b/scripts/plotmpl.py @@ -890,9 +890,9 @@ def main(csv_paths, output, *, # figure out formats/colors here so that subplot defines don't change # them later, that'd be bad - dataformats_ = {name: formats_[i, name] + dataformats_ = {name: punescape(formats_[i, name], dataattrs_[name]) for i, name in enumerate(datasets_.keys())} - datacolors_ = {name: colors_[i, name] + datacolors_ = {name: punescape(colors_[i, name], dataattrs_[name]) for i, name in enumerate(datasets_.keys())} datalabels_ = {name: punescape(labels_[i, name], dataattrs_[name]) for i, name in enumerate(datasets_.keys()) @@ -1325,7 +1325,8 @@ if __name__ == "__main__": )(*x.split('=', 1)) if '=' in x else x.strip(), help="Add a color to use. Can be assigned to a specific group " - "where a group is the comma-separated 'by' fields.") + "where a group is the comma-separated 'by' fields. Accepts %% " + "modifiers.") parser.add_argument( '-F', '--add-format', dest='formats', @@ -1338,7 +1339,7 @@ if __name__ == "__main__": if '=' in x else x.strip(), help="Add a matplotlib format to use. Can be assigned to a " "specific group where a group is the comma-separated 'by' " - "fields.") + "fields. Accepts %% modifiers.") parser.add_argument( '-.', '--points', action='store_true', diff --git a/scripts/treemap.py b/scripts/treemap.py index 908ae7bc..fab2cd46 100755 --- a/scripts/treemap.py +++ b/scripts/treemap.py @@ -21,7 +21,7 @@ import shutil # we don't actually need that many chars/colors thanks to the # 4-colorability of all 2d maps CHARS = ['.'] -COLORS = [34, 31, 32, 35, 33, 36] +COLORS = ['34', '31', '32', '35', '33', '36'] CHARS_DOTS = " .':" CHARS_BRAILLE = ( @@ -295,6 +295,17 @@ def punescape(s, attrs=None): else: assert False return re.sub(pattern, unescape, s) +# split %-escaped strings into chars +def psplit(s): + pattern = re.compile( + '%[%n]' + '|' '%x..' + '|' '%u....' + '|' '%U........' + '|' '%\((?P[^)]*)\)' + '(?P[+\- #0-9\.]*[sdboxXfFeEgG])') + return [m.group() for m in re.finditer(pattern.pattern + '|.', s)] + # a little ascii renderer class Canvas: @@ -740,9 +751,9 @@ def main(csv_paths, *, chars_ = [] for char in chars: if isinstance(char, tuple): - chars_.extend((char[0], c) for c in char[1]) + chars_.extend((char[0], c) for c in psplit(char[1])) else: - chars_.extend(char) + chars_.extend(psplit(char)) chars_ = Attr(chars_, defaults=CHARS) colors_ = Attr(colors, defaults=COLORS) @@ -821,11 +832,11 @@ def main(csv_paths, *, # use colors for top of tree for i, t in enumerate(tile.children): for t_ in t.tiles(): - t_.color = colors_[i, t.key] + t_.color = punescape(colors_[i, t.key], t_.attrs) # and chars/labels for bottom of tree for i, t in enumerate(tile.leaves()): - t.char = chars_[i, t.key] + t.char = punescape(chars_[i, t.key], t.attrs)[0] # limit to 1 char if (i, t.key) in labels_: t.label = punescape(labels_[i, t.key], t.attrs) @@ -1061,7 +1072,8 @@ if __name__ == "__main__": )(*x.split('=', 1)) if '=' in x else x.strip(), help="Add characters to use. Can be assigned to a specific group " - "where a group is the comma-separated 'by' fields.") + "where a group is the comma-separated 'by' fields. Accepts %% " + "modifiers.") parser.add_argument( '-C', '--add-color', dest='colors', @@ -1073,7 +1085,8 @@ if __name__ == "__main__": )(*x.split('=', 1)) if '=' in x else x.strip(), help="Add a color to use. Can be assigned to a specific group " - "where a group is the comma-separated 'by' fields.") + "where a group is the comma-separated 'by' fields. Accepts %% " + "modifiers.") parser.add_argument( '--color', choices=['never', 'always', 'auto'], diff --git a/scripts/treemapd3.py b/scripts/treemapd3.py index 7e727a6d..49092153 100755 --- a/scripts/treemapd3.py +++ b/scripts/treemapd3.py @@ -661,7 +661,7 @@ def main(csv_paths, output, *, # use colors for top of tree for i, t in enumerate(tile.children): for t_ in t.tiles(): - t_.color = colors_[i, t_.key] + t_.color = punescape(colors_[i, t_.key], t_.attrs) # and labels everywhere for i, t in enumerate(tile.tiles()): @@ -969,7 +969,8 @@ if __name__ == "__main__": )(*x.split('=', 1)) if '=' in x else x.strip(), help="Add a color to use. Can be assigned to a specific group " - "where a group is the comma-separated 'by' fields.") + "where a group is the comma-separated 'by' fields. Accepts %% " + "modifiers.") parser.add_argument( '-W', '--width', type=lambda x: int(x, 0),