scripts: plot[mpl].py: Adopted -s/--sort and -S for legend sorting

Before this, the only option for ordering the legend was by specifying
explicit -L/--add-label labels. This works for the most part, but
doesn't cover the case where you don't know the parameterization of the
input data.

And we already have -s/-S flags in other csv scripts, so it makes sense
to adopt them in plot.py/plotmpl.py to allow sorting by one or more
explicit fields.

Note that -s/-S can be combined with explicit -L/--add-labels to order
datasets with the same sort field:

  $ ./scripts/plot.py bench.csv \
          -bBLOCK_SIZE \
          -xn \
          -ybench_readed \
          -ybench_proged \
          -ybench_erased \
          --legend \
          -sBLOCK_SIZE \
          -L'*,bench_readed=bs=%(BLOCK_SIZE)s' \
          -L'*,bench_proged=' \
          -L'*,bench_erased='

---

Unfortunately this conflicted with -s/--sleep, which is a common flag in
the ascii-art scripts. This was bound to conflict with -s/--sort
eventually, so a came up with some alternatives:

- -s/--sleep -> -~/--sleep
- -S/--coalesce -> -+/--coalesce

But I'll admit I'm not the happiest about these...
This commit is contained in:
Christopher Haster
2025-05-15 15:51:49 -05:00
parent d4c772907d
commit c04f36ead4
8 changed files with 85 additions and 15 deletions

View File

@ -1711,7 +1711,7 @@ if __name__ == "__main__":
action='store_true',
help="Pipe directly to stdout.")
parser.add_argument(
'-s', '--sleep',
'-~', '--sleep',
type=float,
help="Time in seconds to sleep between redraws when running "
"with -k. Defaults to 2 seconds.")

View File

@ -5161,7 +5161,7 @@ if __name__ == "__main__":
action='store_true',
help="Pipe directly to stdout.")
parser.add_argument(
'-s', '--sleep',
'-~', '--sleep',
type=float,
help="Time in seconds to sleep between redraws when running "
"with -k. Defaults to 2 seconds.")

View File

@ -1845,11 +1845,11 @@ if __name__ == "__main__":
action='store_true',
help="Pipe directly to stdout.")
parser.add_argument(
'-S', '--coalesce',
'-+', '--coalesce',
type=lambda x: int(x, 0),
help="Number of operations to coalesce together.")
parser.add_argument(
'-s', '--sleep',
'-~', '--sleep',
type=float,
help="Seconds to sleep between draws, coalescing operations "
"in between.")

View File

@ -314,6 +314,19 @@ def dat(x, *args):
else:
raise
# a simple reverse-key class
class Rev(co.namedtuple('Rev', 'a')):
__slots__ = ()
# yes we need all of these because we're a namedtuple
def __lt__(self, other):
return self.a > other.a
def __gt__(self, other):
return self.a < other.a
def __le__(self, other):
return self.a >= other.a
def __ge__(self, other):
return self.a <= other.a
def collect(csv_paths, defines=[]):
# collect results from CSV files
fields = []
@ -1185,6 +1198,7 @@ def main_(ring, csv_paths, *,
x=None,
y=None,
define=[],
sort=None,
labels=[],
chars=[],
line_chars=[],
@ -1384,12 +1398,18 @@ def main_(ring, csv_paths, *,
# note we don't need to filter by defines again
datasets_, dataattrs_ = fold(results, all_by, all_x, all_y)
# order by labels
# sort datasets
datasets_ = co.OrderedDict(sorted(
datasets_.items(),
key=lambda kv: labels_.key(kv[0])))
key=lambda kv: (
# sort by explicit sort fields
tuple((Rev if reverse else lambda x: x)(
dat(dataattrs_[kv[0]].get(k,''), 0))
for k, reverse in (sort or [])),
# order by labels
labels_.key(kv[0]))))
# and merge dataattrs
# merge dataattrs
mergedattrs_ = {k: v
for dataattr in dataattrs_.values()
for k, v in dataattr.items()}
@ -1918,6 +1938,21 @@ if __name__ == "__main__":
action='append',
help="Only include results where this field is this value. May "
"include comma-separated options and globs.")
class AppendSort(argparse.Action):
def __call__(self, parser, namespace, value, option):
if namespace.sort is None:
namespace.sort = []
namespace.sort.append((value, option in {'-S', '--reverse-sort'}))
parser.add_argument(
'-s', '--sort',
nargs='?',
action=AppendSort,
help="Sort by this field.")
parser.add_argument(
'-S', '--reverse-sort',
nargs='?',
action=AppendSort,
help="Sort by this field, but backwards.")
parser.add_argument(
'-L', '--add-label',
dest='labels',
@ -2126,7 +2161,7 @@ if __name__ == "__main__":
action='store_true',
help="Pipe directly to stdout.")
parser.add_argument(
'-s', '--sleep',
'-~', '--sleep',
type=float,
help="Time in seconds to sleep between redraws when running "
"with -k. Defaults to 2 seconds.")

View File

@ -198,6 +198,19 @@ def dat(x, *args):
else:
raise
# a simple reverse-key class
class Rev(co.namedtuple('Rev', 'a')):
__slots__ = ()
# yes we need all of these because we're a namedtuple
def __lt__(self, other):
return self.a > other.a
def __gt__(self, other):
return self.a < other.a
def __le__(self, other):
return self.a >= other.a
def __ge__(self, other):
return self.a <= other.a
def collect(csv_paths, defines=[]):
# collect results from CSV files
fields = []
@ -779,6 +792,7 @@ def main(csv_paths, output, *,
x=None,
y=None,
define=[],
sort=None,
labels=[],
colors=[],
formats=[],
@ -949,12 +963,18 @@ def main(csv_paths, output, *,
# note we don't need to filter by defines again
datasets_, dataattrs_ = fold(results, all_by, all_x, all_y)
# order by labels
# sort datasets
datasets_ = co.OrderedDict(sorted(
datasets_.items(),
key=lambda kv: labels_.key(kv[0])))
key=lambda kv: (
# sort by explicit sort fields
tuple((Rev if reverse else lambda x: x)(
dat(dataattrs_[kv[0]].get(k,''), 0))
for k, reverse in (sort or [])),
# order by labels
labels_.key(kv[0]))))
# and merge dataattrs
# merge dataattrs
mergedattrs_ = {k: v
for dataattr in dataattrs_.values()
for k, v in dataattr.items()}
@ -1372,6 +1392,21 @@ if __name__ == "__main__":
action='append',
help="Only include results where this field is this value. May "
"include comma-separated options and globs.")
class AppendSort(argparse.Action):
def __call__(self, parser, namespace, value, option):
if namespace.sort is None:
namespace.sort = []
namespace.sort.append((value, option in {'-S', '--reverse-sort'}))
parser.add_argument(
'-s', '--sort',
nargs='?',
action=AppendSort,
help="Sort by this field.")
parser.add_argument(
'-S', '--reverse-sort',
nargs='?',
action=AppendSort,
help="Sort by this field, but backwards.")
parser.add_argument(
'-L', '--add-label',
dest='labels',

View File

@ -236,11 +236,11 @@ if __name__ == "__main__":
action='store_true',
help="Pipe directly to stdout.")
parser.add_argument(
'-S', '--coalesce',
'-+', '--coalesce',
type=lambda x: int(x, 0),
help="Number of lines to coalesce together.")
parser.add_argument(
'-s', '--sleep',
'-~', '--sleep',
type=float,
help="Seconds to sleep between draws, coalescing lines in "
"between.")

View File

@ -1515,7 +1515,7 @@ if __name__ == "__main__":
action='store_true',
help="Pipe directly to stdout.")
parser.add_argument(
'-s', '--sleep',
'-~', '--sleep',
type=float,
help="Time in seconds to sleep between redraws when running "
"with -k. Defaults to 2 seconds.")

View File

@ -329,7 +329,7 @@ if __name__ == "__main__":
action='store_true',
help="Pipe directly to stdout.")
parser.add_argument(
'-s', '--sleep',
'-~', '--sleep',
type=float,
help="Seconds to sleep between runs. Defaults to 2 seconds.")
parser.add_argument(