scripts: plot[mpl].py: Reworked --add-xticklabel/yticklabel
This adopts the Attr rework for the --add-xticklabel and
--add-yticklabel flags.
Sort of.
These require a bit of special behavior to make work, but should at
least be externally consistent with the other Attr flags.
Instead of assigning to by-field groups, --add-xticklabel/yticklabel
assign to the relevant x/y coord:
$ ./scripts/plotmpl.py \
--add-xticklabel='0=zero' \
--add-yticklabel='100=one-hundred'
The real power comes from our % modifiers. As a special case,
--add-xticklabel/yticklabel can reference the special x/y field, which
represents the current x/y coord:
$ ./scripts/plotmpl.py --y2 --yticks=5 --add-yticklabel='%(y)d KiB'
Combined with format specifiers, this allows for quite a bit:
$ ./scripts/plotmpl.py --y2 --yticks=5 --add-yticklabel='0x%(y)04x'
---
Note that plot.py only shows the min/max x/yticks, so plot.py only
accepts indexed --add-xticklabel/yticklabels, and will error if the
assigning variant is used.
This commit is contained in:
+20
-18
@@ -495,19 +495,20 @@ def punescape(s, attrs=None):
|
|||||||
v = attrs(m.group('field'))
|
v = attrs(m.group('field'))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return m.group()
|
return m.group()
|
||||||
if m.group('format')[-1] in 'dboxX':
|
f = m.group('format')
|
||||||
|
if f[-1] in 'dboxX':
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
v = try_dat(v) or 0
|
v = try_dat(v) or 0
|
||||||
v = int(v)
|
v = int(v)
|
||||||
elif m.group('format')[-1] in 'fFeEgG':
|
elif f[-1] in 'fFeEgG':
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
v = try_dat(v) or 0
|
v = try_dat(v) or 0
|
||||||
v = float(v)
|
v = float(v)
|
||||||
else:
|
else:
|
||||||
|
f = ('<' if '-' in f else '>') + f.replace('-', '')
|
||||||
v = str(v)
|
v = str(v)
|
||||||
# note we need Python's new format syntax for binary
|
# note we need Python's new format syntax for binary
|
||||||
f = '{:%s}' % m.group('format')
|
return ('{:%s}' % f).format(v)
|
||||||
return f.format(v)
|
|
||||||
else: assert False
|
else: assert False
|
||||||
return re.sub(pattern, unescape, s)
|
return re.sub(pattern, unescape, s)
|
||||||
|
|
||||||
@@ -1121,7 +1122,7 @@ def main(csv_paths, *,
|
|||||||
else max(
|
else max(
|
||||||
# bit of a hack, we just guess the yticklabel size
|
# bit of a hack, we just guess the yticklabel size
|
||||||
# since we don't have the data yet
|
# since we don't have the data yet
|
||||||
(len(punescape(l)) for l in s.yticklabels),
|
(len(punescape(l, {'y': 0})) for l in s.yticklabels),
|
||||||
default=0))
|
default=0))
|
||||||
+ (1 if s.yticklabels != [] else 0),
|
+ (1 if s.yticklabels != [] else 0),
|
||||||
)
|
)
|
||||||
@@ -1298,7 +1299,8 @@ def main(csv_paths, *,
|
|||||||
if s.xticklabels is None
|
if s.xticklabels is None
|
||||||
# bit of a hack, we just guess the xticklabel size
|
# bit of a hack, we just guess the xticklabel size
|
||||||
# since we don't have the data yet
|
# since we don't have the data yet
|
||||||
else sum(len(punescape(l)) for l in s.xticklabels))
|
else sum(len(punescape(l, {'x': 0}))
|
||||||
|
for l in s.xticklabels))
|
||||||
# fit yunits
|
# fit yunits
|
||||||
minheight = sum(s.ymargin) + 2
|
minheight = sum(s.ymargin) + 2
|
||||||
|
|
||||||
@@ -1390,10 +1392,12 @@ def main(csv_paths, *,
|
|||||||
subxlabel = [punescape(l, submergedattrs) for l in s.xlabel]
|
subxlabel = [punescape(l, submergedattrs) for l in s.xlabel]
|
||||||
subylabel = [punescape(l, submergedattrs) for l in s.ylabel]
|
subylabel = [punescape(l, submergedattrs) for l in s.ylabel]
|
||||||
subxticklabels = (
|
subxticklabels = (
|
||||||
[punescape(l, submergedattrs) for l in s.xticklabels]
|
[punescape(l, submergedattrs | {'x': x})
|
||||||
|
for l, x in zip(s.xticklabels, xlim_)]
|
||||||
if s.xticklabels is not None else None)
|
if s.xticklabels is not None else None)
|
||||||
subyticklabels = (
|
subyticklabels = (
|
||||||
[punescape(l, submergedattrs) for l in s.yticklabels]
|
[punescape(l, submergedattrs | {'y': y})
|
||||||
|
for l, y in zip(s.yticklabels, ylim_)]
|
||||||
if s.yticklabels is not None else None)
|
if s.yticklabels is not None else None)
|
||||||
|
|
||||||
# find actual width/height
|
# find actual width/height
|
||||||
@@ -1790,17 +1794,15 @@ if __name__ == "__main__":
|
|||||||
'--ylabel',
|
'--ylabel',
|
||||||
help="Add a label to the y-axis. Accepts %% modifiers.")
|
help="Add a label to the y-axis. Accepts %% modifiers.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--xticklabels',
|
'--add-xticklabel',
|
||||||
type=lambda x: [x.strip() for x in re.split(r'(?<!%),', x)]
|
dest='xticklabels',
|
||||||
if x.strip() else [],
|
action='append',
|
||||||
help="Comma separated xticklabels. Accepts %%, and other "
|
help="Add an xticklabel. Accepts %% modifiers.")
|
||||||
"%% modifiers.")
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--yticklabels',
|
'--add-yticklabel',
|
||||||
type=lambda x: [x.strip() for x in re.split(r'(?<!%),', x)]
|
dest='yticklabels',
|
||||||
if x.strip() else [],
|
action='append',
|
||||||
help="Comma separated yticklabels. Accepts %%, and other "
|
help="Add an yticklabel. Accepts %% modifiers.")
|
||||||
"%% modifiers.")
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--title',
|
'--title',
|
||||||
help="Add a title. Accepts %% modifiers.")
|
help="Add a title. Accepts %% modifiers.")
|
||||||
|
|||||||
+99
-53
@@ -420,15 +420,20 @@ def punescape(s, attrs=None):
|
|||||||
v = attrs(m.group('field'))
|
v = attrs(m.group('field'))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return m.group()
|
return m.group()
|
||||||
if m.group('format')[-1] in 'dboxXfFeEgG':
|
f = m.group('format')
|
||||||
|
if f[-1] in 'dboxX':
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
v = try_dat(v) or 0
|
v = try_dat(v) or 0
|
||||||
|
v = int(v)
|
||||||
|
elif f[-1] in 'fFeEgG':
|
||||||
|
if isinstance(v, str):
|
||||||
|
v = try_dat(v) or 0
|
||||||
|
v = float(v)
|
||||||
else:
|
else:
|
||||||
if not isinstance(v, str):
|
f = ('<' if '-' in f else '>') + f.replace('-', '')
|
||||||
v = str(v)
|
v = str(v)
|
||||||
# note we need Python's new format syntax for binary
|
# note we need Python's new format syntax for binary
|
||||||
f = '{:%s}' % m.group('format')
|
return ('{:%s}' % f).format(v)
|
||||||
return f.format(v)
|
|
||||||
else: assert False
|
else: assert False
|
||||||
return re.sub(pattern, unescape, s)
|
return re.sub(pattern, unescape, s)
|
||||||
|
|
||||||
@@ -1020,63 +1025,96 @@ def main(csv_paths, output, *,
|
|||||||
for dataset in subdatasets.values()
|
for dataset in subdatasets.values()
|
||||||
for _, y in dataset
|
for _, y in dataset
|
||||||
if y is not None))))
|
if y is not None))))
|
||||||
# axes ticks
|
# x-axes ticks
|
||||||
if x2_:
|
if xticklabels_ and any(isinstance(l, tuple) for l in xticklabels_):
|
||||||
ax.xaxis.set_major_formatter(lambda x, pos:
|
ax.xaxis.set_major_locator(mpl.ticker.FixedLocator([
|
||||||
si2(x)+(xunits_ if xunits_ else ''))
|
x for x, _ in xticklabels_]))
|
||||||
if xticklabels_ is not None:
|
ax.xaxis.set_major_formatter(mpl.ticker.FixedFormatter([
|
||||||
ax.xaxis.set_ticklabels([punescape(l, submergedattrs)
|
punescape(l, submergedattrs | {'x': x})
|
||||||
for l in xticklabels_])
|
for x, l in xticklabels_]))
|
||||||
|
elif x2_:
|
||||||
if xticks_ is None:
|
if xticks_ is None:
|
||||||
ax.xaxis.set_major_locator(AutoMultipleLocator(2))
|
ax.xaxis.set_major_locator(AutoMultipleLocator(2))
|
||||||
elif isinstance(xticks_, list):
|
|
||||||
ax.xaxis.set_major_locator(mpl.ticker.FixedLocator(xticks_))
|
|
||||||
elif xticks_ != 0:
|
elif xticks_ != 0:
|
||||||
ax.xaxis.set_major_locator(AutoMultipleLocator(2, xticks_-1))
|
ax.xaxis.set_major_locator(AutoMultipleLocator(2, xticks_-1))
|
||||||
else:
|
else:
|
||||||
ax.xaxis.set_major_locator(mpl.ticker.NullLocator())
|
ax.xaxis.set_major_locator(mpl.ticker.NullLocator())
|
||||||
|
if xticklabels_:
|
||||||
|
ax.xaxis.set_major_formatter(mpl.ticker.FuncFormatter(
|
||||||
|
(lambda xticklabels_: lambda x, pos:
|
||||||
|
punescape(
|
||||||
|
xticklabels_[pos % len(xticklabels_)],
|
||||||
|
submergedattrs | {'x': x})
|
||||||
|
)(xticklabels_)))
|
||||||
|
else:
|
||||||
|
ax.xaxis.set_major_formatter(mpl.ticker.FuncFormatter(
|
||||||
|
(lambda xunits_: lambda x, pos:
|
||||||
|
si2(x)+(xunits_ if xunits_ else '')
|
||||||
|
)(xunits_)))
|
||||||
else:
|
else:
|
||||||
ax.xaxis.set_major_formatter(lambda x, pos:
|
|
||||||
si(x)+(xunits_ if xunits_ else ''))
|
|
||||||
if xticklabels_ is not None:
|
|
||||||
ax.xaxis.set_ticklabels([punescape(l, submergedattrs)
|
|
||||||
for l in xticklabels_])
|
|
||||||
if xticks_ is None:
|
if xticks_ is None:
|
||||||
ax.xaxis.set_major_locator(mpl.ticker.AutoLocator())
|
ax.xaxis.set_major_locator(mpl.ticker.AutoLocator())
|
||||||
elif isinstance(xticks_, list):
|
|
||||||
ax.xaxis.set_major_locator(mpl.ticker.FixedLocator(xticks_))
|
|
||||||
elif xticks_ != 0:
|
elif xticks_ != 0:
|
||||||
ax.xaxis.set_major_locator(mpl.ticker.MaxNLocator(xticks_-1))
|
ax.xaxis.set_major_locator(mpl.ticker.MaxNLocator(xticks_-1))
|
||||||
else:
|
else:
|
||||||
ax.xaxis.set_major_locator(mpl.ticker.NullLocator())
|
ax.xaxis.set_major_locator(mpl.ticker.NullLocator())
|
||||||
if y2_:
|
if xticklabels_:
|
||||||
ax.yaxis.set_major_formatter(lambda x, pos:
|
ax.xaxis.set_major_formatter(mpl.ticker.FuncFormatter(
|
||||||
si2(x)+(yunits_ if yunits_ else ''))
|
(lambda xticklabels_: lambda x, pos:
|
||||||
if yticklabels_ is not None:
|
punescape(
|
||||||
ax.xaxis.set_ticklabels([punescape(l, submergedattrs)
|
xticklabels_[pos % len(xticklabels_)],
|
||||||
for l in yticklabels_])
|
submergedattrs | {'x': x})
|
||||||
|
)(xticklabels_)))
|
||||||
|
else:
|
||||||
|
ax.xaxis.set_major_formatter(mpl.ticker.FuncFormatter(
|
||||||
|
(lambda xunits_: lambda x, pos:
|
||||||
|
si(x)+(xunits_ if xunits_ else '')
|
||||||
|
)(xunits_)))
|
||||||
|
# y-axes ticks
|
||||||
|
if yticklabels_ and any(isinstance(l, tuple) for l in yticklabels_):
|
||||||
|
ax.yaxis.set_major_locator(mpl.ticker.FixedLocator([
|
||||||
|
y for y, _ in yticklabels_]))
|
||||||
|
ax.yaxis.set_major_formatter(mpl.ticker.FixedFormatter([
|
||||||
|
punescape(l, submergedattrs | {'y': y})
|
||||||
|
for y, l in yticklabels_]))
|
||||||
|
elif y2_:
|
||||||
if yticks_ is None:
|
if yticks_ is None:
|
||||||
ax.yaxis.set_major_locator(AutoMultipleLocator(2))
|
ax.yaxis.set_major_locator(AutoMultipleLocator(2))
|
||||||
elif isinstance(yticks_, list):
|
|
||||||
ax.yaxis.set_major_locator(mpl.ticker.FixedLocator(yticks_))
|
|
||||||
elif yticks_ != 0:
|
elif yticks_ != 0:
|
||||||
ax.yaxis.set_major_locator(AutoMultipleLocator(2, yticks_-1))
|
ax.yaxis.set_major_locator(AutoMultipleLocator(2, yticks_-1))
|
||||||
else:
|
else:
|
||||||
ax.yaxis.set_major_locator(mpl.ticker.NullLocator())
|
ax.yaxis.set_major_locator(mpl.ticker.NullLocator())
|
||||||
|
if yticklabels_:
|
||||||
|
ax.yaxis.set_major_formatter(mpl.ticker.FuncFormatter(
|
||||||
|
(lambda yticklabels_: lambda y, pos:
|
||||||
|
punescape(
|
||||||
|
yticklabels_[pos % len(yticklabels_)],
|
||||||
|
submergedattrs | {'y': y})
|
||||||
|
)(yticklabels_)))
|
||||||
|
else:
|
||||||
|
ax.yaxis.set_major_formatter(mpl.ticker.FuncFormatter(
|
||||||
|
(lambda yunits_: lambda y, pos:
|
||||||
|
si2(y)+(yunits_ if yunits_ else '')
|
||||||
|
)(yunits_)))
|
||||||
else:
|
else:
|
||||||
ax.yaxis.set_major_formatter(lambda x, pos:
|
|
||||||
si(x)+(yunits_ if yunits_ else ''))
|
|
||||||
if yticklabels_ is not None:
|
|
||||||
ax.xaxis.set_ticklabels([punescape(l, submergedattrs)
|
|
||||||
for l in yticklabels_])
|
|
||||||
if yticks_ is None:
|
if yticks_ is None:
|
||||||
ax.yaxis.set_major_locator(mpl.ticker.AutoLocator())
|
ax.yaxis.set_major_locator(mpl.ticker.AutoLocator())
|
||||||
elif isinstance(yticks_, list):
|
|
||||||
ax.yaxis.set_major_locator(mpl.ticker.FixedLocator(yticks_))
|
|
||||||
elif yticks_ != 0:
|
elif yticks_ != 0:
|
||||||
ax.yaxis.set_major_locator(mpl.ticker.MaxNLocator(yticks_-1))
|
ax.yaxis.set_major_locator(mpl.ticker.MaxNLocator(yticks_-1))
|
||||||
else:
|
else:
|
||||||
ax.yaxis.set_major_locator(mpl.ticker.NullLocator())
|
ax.yaxis.set_major_locator(mpl.ticker.NullLocator())
|
||||||
|
if yticklabels_:
|
||||||
|
ax.yaxis.set_major_formatter(mpl.ticker.FuncFormatter(
|
||||||
|
(lambda yticklabels_: lambda y, pos:
|
||||||
|
punescape(
|
||||||
|
yticklabels_[pos % len(yticklabels_)],
|
||||||
|
submergedattrs | {'y': y})
|
||||||
|
)(yticklabels_)))
|
||||||
|
else:
|
||||||
|
ax.yaxis.set_major_formatter(mpl.ticker.FuncFormatter(
|
||||||
|
(lambda yunits_: lambda y, pos:
|
||||||
|
si(y)+(yunits_ if yunits_ else '')
|
||||||
|
)(yunits_)))
|
||||||
if ggplot:
|
if ggplot:
|
||||||
ax.grid(sketch_params=None)
|
ax.grid(sketch_params=None)
|
||||||
|
|
||||||
@@ -1349,16 +1387,16 @@ if __name__ == "__main__":
|
|||||||
help="Use base-2 prefixes for the y-axis.")
|
help="Use base-2 prefixes for the y-axis.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--xticks',
|
'--xticks',
|
||||||
type=lambda x: int(x, 0) if ',' not in x
|
type=lambda x: int(x, 0),
|
||||||
else [dat(x) for x in x.split(',')],
|
help="Number of ticks for the x-axis, or 0 to disable. "
|
||||||
help="Ticks for the x-axis. This can be explicit comma-separated "
|
"Alternatively, --add-xticklabel can provide explicit tick "
|
||||||
"ticks, the number of ticks, or 0 to disable.")
|
"locations.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--yticks',
|
'--yticks',
|
||||||
type=lambda x: int(x, 0) if ',' not in x
|
type=lambda x: int(x, 0),
|
||||||
else [dat(x) for x in x.split(',')],
|
help="Number of ticks for the y-axis, or 0 to disable. "
|
||||||
help="Ticks for the y-axis. This can be explicit comma-separated "
|
"Alternatively, --add-yticklabel can provide explicit tick "
|
||||||
"ticks, the number of ticks, or 0 to disable.")
|
"locations.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--xunits',
|
'--xunits',
|
||||||
help="Units for the x-axis.")
|
help="Units for the x-axis.")
|
||||||
@@ -1372,17 +1410,25 @@ if __name__ == "__main__":
|
|||||||
'--ylabel',
|
'--ylabel',
|
||||||
help="Add a label to the y-axis. Accepts %% modifiers.")
|
help="Add a label to the y-axis. Accepts %% modifiers.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--xticklabels',
|
'--add-xticklabel',
|
||||||
type=lambda x: [x.strip() for x in re.split(r'(?<!%),', x)]
|
dest='xticklabels',
|
||||||
if x.strip() else [],
|
action='append',
|
||||||
help="Comma separated xticklabels. Accepts %%, and other "
|
type=lambda x: (
|
||||||
"%%-escaped codes.")
|
lambda k, v: (dat(k), v.strip())
|
||||||
|
)(*x.split('=', 1))
|
||||||
|
if '=' in x else x.strip(),
|
||||||
|
help="Add an xticklabel. Can be assigned to an explicit "
|
||||||
|
"location. Accepts %% modifiers.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--yticklabels',
|
'--add-yticklabel',
|
||||||
type=lambda x: [x.strip() for x in re.split(r'(?<!%),', x)]
|
dest='yticklabels',
|
||||||
if x.strip() else [],
|
action='append',
|
||||||
help="Comma separated yticklabels. Accepts %%, and other "
|
type=lambda x: (
|
||||||
"%%-escaped codes.")
|
lambda k, v: (dat(k), v.strip())
|
||||||
|
)(*x.split('=', 1))
|
||||||
|
if '=' in x else x.strip(),
|
||||||
|
help="Add an yticklabel. Can be assigned to an explicit "
|
||||||
|
"location. Accepts %% modifiers.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--title',
|
'--title',
|
||||||
help="Add a title. Accepts %% modifiers.")
|
help="Add a title. Accepts %% modifiers.")
|
||||||
|
|||||||
+9
-4
@@ -279,15 +279,20 @@ def punescape(s, attrs=None):
|
|||||||
v = attrs(m.group('field'))
|
v = attrs(m.group('field'))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return m.group()
|
return m.group()
|
||||||
if m.group('format')[-1] in 'dboxXfFeEgG':
|
f = m.group('format')
|
||||||
|
if f[-1] in 'dboxX':
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
v = try_dat(v) or 0
|
v = try_dat(v) or 0
|
||||||
|
v = int(v)
|
||||||
|
elif f[-1] in 'fFeEgG':
|
||||||
|
if isinstance(v, str):
|
||||||
|
v = try_dat(v) or 0
|
||||||
|
v = float(v)
|
||||||
else:
|
else:
|
||||||
if not isinstance(v, str):
|
f = ('<' if '-' in f else '>') + f.replace('-', '')
|
||||||
v = str(v)
|
v = str(v)
|
||||||
# note we need Python's new format syntax for binary
|
# note we need Python's new format syntax for binary
|
||||||
f = '{:%s}' % m.group('format')
|
return ('{:%s}' % f).format(v)
|
||||||
return f.format(v)
|
|
||||||
else: assert False
|
else: assert False
|
||||||
return re.sub(pattern, unescape, s)
|
return re.sub(pattern, unescape, s)
|
||||||
|
|
||||||
|
|||||||
@@ -295,15 +295,20 @@ def punescape(s, attrs=None):
|
|||||||
v = attrs(m.group('field'))
|
v = attrs(m.group('field'))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return m.group()
|
return m.group()
|
||||||
if m.group('format')[-1] in 'dboxXfFeEgG':
|
f = m.group('format')
|
||||||
|
if f[-1] in 'dboxX':
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
v = try_dat(v) or 0
|
v = try_dat(v) or 0
|
||||||
|
v = int(v)
|
||||||
|
elif f[-1] in 'fFeEgG':
|
||||||
|
if isinstance(v, str):
|
||||||
|
v = try_dat(v) or 0
|
||||||
|
v = float(v)
|
||||||
else:
|
else:
|
||||||
if not isinstance(v, str):
|
f = ('<' if '-' in f else '>') + f.replace('-', '')
|
||||||
v = str(v)
|
v = str(v)
|
||||||
# note we need Python's new format syntax for binary
|
# note we need Python's new format syntax for binary
|
||||||
f = '{:%s}' % m.group('format')
|
return ('{:%s}' % f).format(v)
|
||||||
return f.format(v)
|
|
||||||
else: assert False
|
else: assert False
|
||||||
return re.sub(pattern, unescape, s)
|
return re.sub(pattern, unescape, s)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user