mirror of
https://github.com/littlefs-project/littlefs.git
synced 2025-12-26 01:07:23 +00:00
scripts: Adopted Canvas class in plot.py
This should have no noticeable impact on plot.py, but shared classes have proven helpful for maintaining these scripts. Unfortunately, this did require some tweaking of the Canvas class to get things working. Now, instead of storing things in an internal high-resolution grid, the Canvas class only keeps track of the most recent character, with bitmasked ints storing sub-char info. This makes it so sub-char draws overwrite full characters, which is necessary for plot.py's axis/data overlap to work.
This commit is contained in:
@@ -253,6 +253,8 @@ class Canvas:
|
|||||||
else:
|
else:
|
||||||
xscale, yscale = 1, 1
|
xscale, yscale = 1, 1
|
||||||
|
|
||||||
|
self.width_ = width
|
||||||
|
self.height_ = height
|
||||||
self.width = xscale*width
|
self.width = xscale*width
|
||||||
self.height = yscale*height
|
self.height = yscale*height
|
||||||
self.xscale = xscale
|
self.xscale = xscale
|
||||||
@@ -262,50 +264,63 @@ class Canvas:
|
|||||||
self.braille = braille
|
self.braille = braille
|
||||||
|
|
||||||
# create initial canvas
|
# create initial canvas
|
||||||
self.grid = [False] * (self.width*self.height)
|
self.chars = [0] * (width*height)
|
||||||
self.colors = [''] * (self.width*self.height)
|
self.colors = [''] * (width*height)
|
||||||
|
|
||||||
def __getitem__(self, xy):
|
def char(self, x, y, char=None):
|
||||||
x, y = xy
|
|
||||||
# ignore out of bounds
|
# ignore out of bounds
|
||||||
if x < 0 or y < 0 or x >= self.width or y >= self.height:
|
if x < 0 or y < 0 or x >= self.width or y >= self.height:
|
||||||
return
|
return False
|
||||||
|
|
||||||
return self.grid[x + y*self.width]
|
x_ = x // self.xscale
|
||||||
|
y_ = y // self.yscale
|
||||||
def __setitem__(self, xy, char):
|
if char is not None:
|
||||||
x, y = xy
|
c = self.chars[x_ + y_*self.width_]
|
||||||
# ignore out of bounds
|
# mask in sub-char pixel?
|
||||||
if x < 0 or y < 0 or x >= self.width or y >= self.height:
|
if isinstance(char, bool):
|
||||||
return
|
if not isinstance(c, int):
|
||||||
|
c = 0
|
||||||
self.grid[x + y*self.width] = char
|
self.chars[x_ + y_*self.width_] = (c
|
||||||
|
| (1
|
||||||
|
<< ((y%self.yscale)*self.xscale
|
||||||
|
+ (self.xscale-1)-(x%self.xscale))))
|
||||||
|
else:
|
||||||
|
self.chars[x_ + y_*self.width_] = char
|
||||||
|
else:
|
||||||
|
c = self.chars[x_ + y_*self.width_]
|
||||||
|
if isinstance(c, int):
|
||||||
|
return ((c
|
||||||
|
>> ((y%self.yscale)*self.xscale
|
||||||
|
+ (self.xscale-1)-(x%self.xscale)))
|
||||||
|
& 1) == 1
|
||||||
|
else:
|
||||||
|
return c
|
||||||
|
|
||||||
def color(self, x, y, color=None):
|
def color(self, x, y, color=None):
|
||||||
# ignore out of bounds
|
# ignore out of bounds
|
||||||
if x < 0 or y < 0 or x >= self.width or y >= self.height:
|
if x < 0 or y < 0 or x >= self.width or y >= self.height:
|
||||||
return
|
return ''
|
||||||
|
|
||||||
|
x_ = x // self.xscale
|
||||||
|
y_ = y // self.yscale
|
||||||
if color is not None:
|
if color is not None:
|
||||||
self.colors[x + y*self.width] = color
|
self.colors[x_ + y_*self.width_] = color
|
||||||
else:
|
else:
|
||||||
return self.colors[x + y*self.width]
|
return self.colors[x_ + y_*self.width_]
|
||||||
|
|
||||||
|
def __getitem__(self, xy):
|
||||||
|
x, y = xy
|
||||||
|
return self.char(x, y)
|
||||||
|
|
||||||
|
def __setitem__(self, xy, char):
|
||||||
|
x, y = xy
|
||||||
|
self.char(x, y, char)
|
||||||
|
|
||||||
def point(self, x, y, *,
|
def point(self, x, y, *,
|
||||||
char=True,
|
char=True,
|
||||||
color=''):
|
color=''):
|
||||||
# make sure non-bool chars map attrs to all points under char
|
self.char(x, y, char)
|
||||||
if not isinstance(char, bool):
|
self.color(x, y, color)
|
||||||
xscale, yscale = self.xscale, self.yscale
|
|
||||||
else:
|
|
||||||
xscale, yscale = 1, 1
|
|
||||||
|
|
||||||
for i in range(xscale*yscale):
|
|
||||||
x_ = x-(x%xscale) + (xscale-1-(i%xscale))
|
|
||||||
y_ = y-(y%yscale) + (i//xscale)
|
|
||||||
|
|
||||||
self[x_, y_] = char
|
|
||||||
self.color(x_, y_, color)
|
|
||||||
|
|
||||||
def line(self, x1, y1, x2, y2, *,
|
def line(self, x1, y1, x2, y2, *,
|
||||||
char=True,
|
char=True,
|
||||||
@@ -318,7 +333,7 @@ class Canvas:
|
|||||||
e = ex + ey
|
e = ex + ey
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
self.point(x1, y1, color=color, char=char)
|
self.point(x1, y1, char=char, color=color)
|
||||||
e2 = 2*e
|
e2 = 2*e
|
||||||
|
|
||||||
if x1 == x2 and y1 == y2:
|
if x1 == x2 and y1 == y2:
|
||||||
@@ -335,7 +350,7 @@ class Canvas:
|
|||||||
e += ex
|
e += ex
|
||||||
y1 += dy
|
y1 += dy
|
||||||
|
|
||||||
self.point(x2, y2, color=color, char=char)
|
self.point(x2, y2, char=char, color=color)
|
||||||
|
|
||||||
def rect(self, x, y, w, h, *,
|
def rect(self, x, y, w, h, *,
|
||||||
char=True,
|
char=True,
|
||||||
@@ -359,47 +374,29 @@ class Canvas:
|
|||||||
x_ += self.xscale
|
x_ += self.xscale
|
||||||
|
|
||||||
def draw(self, row):
|
def draw(self, row):
|
||||||
# scale if needed
|
y_ = self.height_-1 - row
|
||||||
xscale, yscale = self.xscale, self.yscale
|
|
||||||
|
|
||||||
y = self.height//yscale-1 - row
|
|
||||||
row_ = []
|
row_ = []
|
||||||
for x in range(self.width//xscale):
|
for x_ in range(self.width_):
|
||||||
color = ''
|
# char?
|
||||||
char = False
|
c = self.chars[x_ + y_*self.width_]
|
||||||
byte = 0
|
if isinstance(c, int):
|
||||||
for i in range(xscale*yscale):
|
if self.braille:
|
||||||
x_ = x*xscale + (xscale-1-(i%xscale))
|
assert c < 256
|
||||||
y_ = y*yscale + (i//xscale)
|
c = CHARS_BRAILLE[c]
|
||||||
|
elif self.dots:
|
||||||
# calculate char
|
assert c < 4
|
||||||
char_ = self[x_, y_]
|
c = CHARS_DOTS[c]
|
||||||
if char_:
|
|
||||||
byte |= 1 << i
|
|
||||||
if char_ is not True and char_ is not False:
|
|
||||||
char = char_
|
|
||||||
|
|
||||||
# keep track of best color
|
|
||||||
color_ = self.color(x_, y_)
|
|
||||||
if color_:
|
|
||||||
color = color_
|
|
||||||
|
|
||||||
# figure out winning char
|
|
||||||
if byte:
|
|
||||||
if char is not True and char is not False:
|
|
||||||
pass
|
|
||||||
elif self.braille:
|
|
||||||
char = CHARS_BRAILLE[byte]
|
|
||||||
else:
|
else:
|
||||||
char = CHARS_DOTS[byte]
|
assert c < 2
|
||||||
else:
|
c = '.' if c else ' '
|
||||||
char = ' '
|
|
||||||
|
|
||||||
# color?
|
# color?
|
||||||
if byte and self.color_ and color:
|
if self.color_:
|
||||||
char = '\x1b[%sm%s\x1b[m' % (color, char)
|
color = self.colors[x_ + y_*self.width_]
|
||||||
|
if color:
|
||||||
|
c = '\x1b[%sm%s\x1b[m' % (color, c)
|
||||||
|
|
||||||
row_.append(char)
|
row_.append(c)
|
||||||
|
|
||||||
return ''.join(row_)
|
return ''.join(row_)
|
||||||
|
|
||||||
|
|||||||
340
scripts/plot.py
340
scripts/plot.py
@@ -524,6 +524,168 @@ def psplit(s):
|
|||||||
return [m.group() for m in re.finditer(pattern.pattern + '|.', s)]
|
return [m.group() for m in re.finditer(pattern.pattern + '|.', s)]
|
||||||
|
|
||||||
|
|
||||||
|
# a little ascii renderer
|
||||||
|
class Canvas:
|
||||||
|
def __init__(self, width, height, *,
|
||||||
|
color=False,
|
||||||
|
dots=False,
|
||||||
|
braille=False):
|
||||||
|
# scale if we're printing with dots or braille
|
||||||
|
if braille:
|
||||||
|
xscale, yscale = 2, 4
|
||||||
|
elif dots:
|
||||||
|
xscale, yscale = 1, 2
|
||||||
|
else:
|
||||||
|
xscale, yscale = 1, 1
|
||||||
|
|
||||||
|
self.width_ = width
|
||||||
|
self.height_ = height
|
||||||
|
self.width = xscale*width
|
||||||
|
self.height = yscale*height
|
||||||
|
self.xscale = xscale
|
||||||
|
self.yscale = yscale
|
||||||
|
self.color_ = color
|
||||||
|
self.dots = dots
|
||||||
|
self.braille = braille
|
||||||
|
|
||||||
|
# create initial canvas
|
||||||
|
self.chars = [0] * (width*height)
|
||||||
|
self.colors = [''] * (width*height)
|
||||||
|
|
||||||
|
def char(self, x, y, char=None):
|
||||||
|
# ignore out of bounds
|
||||||
|
if x < 0 or y < 0 or x >= self.width or y >= self.height:
|
||||||
|
return False
|
||||||
|
|
||||||
|
x_ = x // self.xscale
|
||||||
|
y_ = y // self.yscale
|
||||||
|
if char is not None:
|
||||||
|
c = self.chars[x_ + y_*self.width_]
|
||||||
|
# mask in sub-char pixel?
|
||||||
|
if isinstance(char, bool):
|
||||||
|
if not isinstance(c, int):
|
||||||
|
c = 0
|
||||||
|
self.chars[x_ + y_*self.width_] = (c
|
||||||
|
| (1
|
||||||
|
<< ((y%self.yscale)*self.xscale
|
||||||
|
+ (self.xscale-1)-(x%self.xscale))))
|
||||||
|
else:
|
||||||
|
self.chars[x_ + y_*self.width_] = char
|
||||||
|
else:
|
||||||
|
c = self.chars[x_ + y_*self.width_]
|
||||||
|
if isinstance(c, int):
|
||||||
|
return ((c
|
||||||
|
>> ((y%self.yscale)*self.xscale
|
||||||
|
+ (self.xscale-1)-(x%self.xscale)))
|
||||||
|
& 1) == 1
|
||||||
|
else:
|
||||||
|
return c
|
||||||
|
|
||||||
|
def color(self, x, y, color=None):
|
||||||
|
# ignore out of bounds
|
||||||
|
if x < 0 or y < 0 or x >= self.width or y >= self.height:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
x_ = x // self.xscale
|
||||||
|
y_ = y // self.yscale
|
||||||
|
if color is not None:
|
||||||
|
self.colors[x_ + y_*self.width_] = color
|
||||||
|
else:
|
||||||
|
return self.colors[x_ + y_*self.width_]
|
||||||
|
|
||||||
|
def __getitem__(self, xy):
|
||||||
|
x, y = xy
|
||||||
|
return self.char(x, y)
|
||||||
|
|
||||||
|
def __setitem__(self, xy, char):
|
||||||
|
x, y = xy
|
||||||
|
self.char(x, y, char)
|
||||||
|
|
||||||
|
def point(self, x, y, *,
|
||||||
|
char=True,
|
||||||
|
color=''):
|
||||||
|
self.char(x, y, char)
|
||||||
|
self.color(x, y, color)
|
||||||
|
|
||||||
|
def line(self, x1, y1, x2, y2, *,
|
||||||
|
char=True,
|
||||||
|
color=''):
|
||||||
|
# incremental error line algorithm
|
||||||
|
ex = abs(x2 - x1)
|
||||||
|
ey = -abs(y2 - y1)
|
||||||
|
dx = +1 if x1 < x2 else -1
|
||||||
|
dy = +1 if y1 < y2 else -1
|
||||||
|
e = ex + ey
|
||||||
|
|
||||||
|
while True:
|
||||||
|
self.point(x1, y1, char=char, color=color)
|
||||||
|
e2 = 2*e
|
||||||
|
|
||||||
|
if x1 == x2 and y1 == y2:
|
||||||
|
break
|
||||||
|
|
||||||
|
if e2 > ey:
|
||||||
|
e += ey
|
||||||
|
x1 += dx
|
||||||
|
|
||||||
|
if x1 == x2 and y1 == y2:
|
||||||
|
break
|
||||||
|
|
||||||
|
if e2 < ex:
|
||||||
|
e += ex
|
||||||
|
y1 += dy
|
||||||
|
|
||||||
|
self.point(x2, y2, char=char, color=color)
|
||||||
|
|
||||||
|
def rect(self, x, y, w, h, *,
|
||||||
|
char=True,
|
||||||
|
color=''):
|
||||||
|
for j in range(h):
|
||||||
|
for i in range(w):
|
||||||
|
self.point(x+i, y+j, char=char, color=color)
|
||||||
|
|
||||||
|
def label(self, x, y, label, width=None, height=None, *,
|
||||||
|
color=''):
|
||||||
|
x_ = x
|
||||||
|
y_ = y
|
||||||
|
for char in label:
|
||||||
|
if char == '\n':
|
||||||
|
x_ = x
|
||||||
|
y_ -= self.yscale
|
||||||
|
else:
|
||||||
|
if ((width is None or x_ < x+width)
|
||||||
|
and (height is None or y_ > y-height)):
|
||||||
|
self.point(x_, y_, char=char, color=color)
|
||||||
|
x_ += self.xscale
|
||||||
|
|
||||||
|
def draw(self, row):
|
||||||
|
y_ = self.height_-1 - row
|
||||||
|
row_ = []
|
||||||
|
for x_ in range(self.width_):
|
||||||
|
# char?
|
||||||
|
c = self.chars[x_ + y_*self.width_]
|
||||||
|
if isinstance(c, int):
|
||||||
|
if self.braille:
|
||||||
|
assert c < 256
|
||||||
|
c = CHARS_BRAILLE[c]
|
||||||
|
elif self.dots:
|
||||||
|
assert c < 4
|
||||||
|
c = CHARS_DOTS[c]
|
||||||
|
else:
|
||||||
|
assert c < 2
|
||||||
|
c = '.' if c else ' '
|
||||||
|
|
||||||
|
# color?
|
||||||
|
if self.color_:
|
||||||
|
color = self.colors[x_ + y_*self.width_]
|
||||||
|
if color:
|
||||||
|
c = '\x1b[%sm%s\x1b[m' % (color, c)
|
||||||
|
|
||||||
|
row_.append(c)
|
||||||
|
|
||||||
|
return ''.join(row_)
|
||||||
|
|
||||||
|
|
||||||
# a hack log that preserves sign, with a linear region between -1 and 1
|
# a hack log that preserves sign, with a linear region between -1 and 1
|
||||||
def symlog(x):
|
def symlog(x):
|
||||||
if x > 1:
|
if x > 1:
|
||||||
@@ -533,30 +695,47 @@ def symlog(x):
|
|||||||
else:
|
else:
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
# our main plot class
|
||||||
class Plot:
|
class Plot:
|
||||||
def __init__(self, width, height, *,
|
def __init__(self, width, height, *,
|
||||||
|
color=False,
|
||||||
|
dots=False,
|
||||||
|
braille=False,
|
||||||
xlim=None,
|
xlim=None,
|
||||||
ylim=None,
|
ylim=None,
|
||||||
xlog=False,
|
xlog=False,
|
||||||
ylog=False,
|
ylog=False):
|
||||||
braille=False,
|
# let Canvas handle braille/dots scaling
|
||||||
dots=False):
|
self.canvas = Canvas(width, height,
|
||||||
# scale if we're printing with dots or braille
|
color=color,
|
||||||
self.width = 2*width if braille else width
|
dots=dots,
|
||||||
self.height = (4*height if braille
|
braille=braille)
|
||||||
else 2*height if dots
|
|
||||||
else height)
|
|
||||||
|
|
||||||
|
# we handle xlim/ylim scaling
|
||||||
self.xlim = xlim or (0, width)
|
self.xlim = xlim or (0, width)
|
||||||
self.ylim = ylim or (0, height)
|
self.ylim = ylim or (0, height)
|
||||||
self.xlog = xlog
|
self.xlog = xlog
|
||||||
self.ylog = ylog
|
self.ylog = ylog
|
||||||
self.braille = braille
|
|
||||||
self.dots = dots
|
|
||||||
|
|
||||||
self.grid = [('',False)]*(self.width*self.height)
|
# go ahead and draw out axis first, we let data overwrite this
|
||||||
|
# to make the best of the limited space
|
||||||
|
for x in range(self.width):
|
||||||
|
self.canvas.point(x, 0, char='-')
|
||||||
|
for y in range(self.height):
|
||||||
|
self.canvas.point(0, y, char='|')
|
||||||
|
self.canvas.point(self.width-1, 0, char='>')
|
||||||
|
self.canvas.point(0, self.height-1, char='^')
|
||||||
|
self.canvas.point(0, 0, char='+')
|
||||||
|
|
||||||
def scale(self, x, y):
|
@property
|
||||||
|
def width(self):
|
||||||
|
return self.canvas.width
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self):
|
||||||
|
return self.canvas.height
|
||||||
|
|
||||||
|
def _scale(self, x, y):
|
||||||
# scale and clamp
|
# scale and clamp
|
||||||
try:
|
try:
|
||||||
if self.xlog:
|
if self.xlog:
|
||||||
@@ -581,131 +760,50 @@ class Plot:
|
|||||||
return x, y
|
return x, y
|
||||||
|
|
||||||
def point(self, x, y, *,
|
def point(self, x, y, *,
|
||||||
color=COLORS[0],
|
char=True,
|
||||||
char=True):
|
color=''):
|
||||||
# scale
|
# scale
|
||||||
x, y = self.scale(x, y)
|
x, y = self._scale(x, y)
|
||||||
|
|
||||||
# ignore out of bounds points
|
# render to canvas
|
||||||
if x >= 0 and x < self.width and y >= 0 and y < self.height:
|
self.canvas.point(x, y,
|
||||||
self.grid[x + y*self.width] = (color, char)
|
char=char,
|
||||||
|
color=color)
|
||||||
|
|
||||||
def line(self, x1, y1, x2, y2, *,
|
def line(self, x1, y1, x2, y2, *,
|
||||||
color=COLORS[0],
|
char=True,
|
||||||
char=True):
|
color=''):
|
||||||
# scale
|
# scale
|
||||||
x1, y1 = self.scale(x1, y1)
|
x1, y1 = self._scale(x1, y1)
|
||||||
x2, y2 = self.scale(x2, y2)
|
x2, y2 = self._scale(x2, y2)
|
||||||
|
|
||||||
# incremental error line algorithm
|
# render to canvas
|
||||||
ex = abs(x2 - x1)
|
self.canvas.line(x1, y1, x2, y2,
|
||||||
ey = -abs(y2 - y1)
|
char=char,
|
||||||
dx = +1 if x1 < x2 else -1
|
color=color)
|
||||||
dy = +1 if y1 < y2 else -1
|
|
||||||
e = ex + ey
|
|
||||||
|
|
||||||
while True:
|
|
||||||
if x1 >= 0 and x1 < self.width and y1 >= 0 and y1 < self.height:
|
|
||||||
self.grid[x1 + y1*self.width] = (color, char)
|
|
||||||
e2 = 2*e
|
|
||||||
|
|
||||||
if x1 == x2 and y1 == y2:
|
|
||||||
break
|
|
||||||
|
|
||||||
if e2 > ey:
|
|
||||||
e += ey
|
|
||||||
x1 += dx
|
|
||||||
|
|
||||||
if x1 == x2 and y1 == y2:
|
|
||||||
break
|
|
||||||
|
|
||||||
if e2 < ex:
|
|
||||||
e += ex
|
|
||||||
y1 += dy
|
|
||||||
|
|
||||||
if x2 >= 0 and x2 < self.width and y2 >= 0 and y2 < self.height:
|
|
||||||
self.grid[x2 + y2*self.width] = (color, char)
|
|
||||||
|
|
||||||
def plot(self, coords, *,
|
def plot(self, coords, *,
|
||||||
color=COLORS[0],
|
|
||||||
char=True,
|
char=True,
|
||||||
line_char=True):
|
line_char=True,
|
||||||
|
color=''):
|
||||||
# draw lines
|
# draw lines
|
||||||
if line_char:
|
if line_char:
|
||||||
for (x1, y1), (x2, y2) in zip(coords, coords[1:]):
|
for (x1, y1), (x2, y2) in zip(coords, coords[1:]):
|
||||||
if y1 is not None and y2 is not None:
|
if y1 is not None and y2 is not None:
|
||||||
self.line(x1, y1, x2, y2,
|
self.line(x1, y1, x2, y2,
|
||||||
color=color,
|
char=line_char,
|
||||||
char=line_char)
|
color=color)
|
||||||
|
|
||||||
# draw points
|
# draw points
|
||||||
if char and (not line_char or char is not True):
|
if char and (not line_char or char is not True):
|
||||||
for x, y in coords:
|
for x, y in coords:
|
||||||
if y is not None:
|
if y is not None:
|
||||||
self.point(x, y,
|
self.point(x, y,
|
||||||
color=color,
|
char=char,
|
||||||
char=char)
|
color=color)
|
||||||
|
|
||||||
def draw(self, row, *,
|
def draw(self, row):
|
||||||
color=False):
|
return self.canvas.draw(row)
|
||||||
# scale if needed
|
|
||||||
if self.braille:
|
|
||||||
xscale, yscale = 2, 4
|
|
||||||
elif self.dots:
|
|
||||||
xscale, yscale = 1, 2
|
|
||||||
else:
|
|
||||||
xscale, yscale = 1, 1
|
|
||||||
|
|
||||||
y = self.height//yscale-1 - row
|
|
||||||
row_ = []
|
|
||||||
for x in range(self.width//xscale):
|
|
||||||
best_f = ''
|
|
||||||
best_c = False
|
|
||||||
|
|
||||||
# encode into a byte
|
|
||||||
b = 0
|
|
||||||
for i in range(xscale*yscale):
|
|
||||||
f, c = self.grid[x*xscale+(xscale-1-(i%xscale))
|
|
||||||
+ (y*yscale+(i//xscale))*self.width]
|
|
||||||
if c:
|
|
||||||
b |= 1 << i
|
|
||||||
|
|
||||||
if f:
|
|
||||||
best_f = f
|
|
||||||
if c and c is not True:
|
|
||||||
best_c = c
|
|
||||||
|
|
||||||
# use byte to lookup character
|
|
||||||
if b:
|
|
||||||
if best_c:
|
|
||||||
c = best_c
|
|
||||||
elif self.braille:
|
|
||||||
c = CHARS_BRAILLE[b]
|
|
||||||
else:
|
|
||||||
c = CHARS_DOTS[b]
|
|
||||||
else:
|
|
||||||
c = ' '
|
|
||||||
|
|
||||||
# color?
|
|
||||||
if b and color and best_f:
|
|
||||||
c = '\x1b[%sm%s\x1b[m' % (best_f, c)
|
|
||||||
|
|
||||||
# draw axis in blank spaces
|
|
||||||
if not b:
|
|
||||||
if x == 0 and y == 0:
|
|
||||||
c = '+'
|
|
||||||
elif x == 0 and y == self.height//yscale-1:
|
|
||||||
c = '^'
|
|
||||||
elif x == self.width//xscale-1 and y == 0:
|
|
||||||
c = '>'
|
|
||||||
elif x == 0:
|
|
||||||
c = '|'
|
|
||||||
elif y == 0:
|
|
||||||
c = '-'
|
|
||||||
|
|
||||||
row_.append(c)
|
|
||||||
|
|
||||||
return ''.join(row_)
|
|
||||||
|
|
||||||
|
|
||||||
# some classes for organizing subplots into a grid
|
# some classes for organizing subplots into a grid
|
||||||
@@ -982,6 +1080,7 @@ def main(csv_paths, *,
|
|||||||
line_chars=[],
|
line_chars=[],
|
||||||
colors=[],
|
colors=[],
|
||||||
color=False,
|
color=False,
|
||||||
|
dots=False,
|
||||||
braille=False,
|
braille=False,
|
||||||
points=False,
|
points=False,
|
||||||
points_and_lines=False,
|
points_and_lines=False,
|
||||||
@@ -1427,12 +1526,13 @@ def main(csv_paths, *,
|
|||||||
plot = Plot(
|
plot = Plot(
|
||||||
subwidth,
|
subwidth,
|
||||||
subheight,
|
subheight,
|
||||||
|
color=color,
|
||||||
|
dots=dots or not line_chars,
|
||||||
|
braille=braille,
|
||||||
xlim=xlim_,
|
xlim=xlim_,
|
||||||
ylim=ylim_,
|
ylim=ylim_,
|
||||||
xlog=xlog_,
|
xlog=xlog_,
|
||||||
ylog=ylog_,
|
ylog=ylog_)
|
||||||
braille=not line_chars and braille,
|
|
||||||
dots=not line_chars and not braille)
|
|
||||||
|
|
||||||
for name, dataset in subdatasets.items():
|
for name, dataset in subdatasets.items():
|
||||||
plot.plot(
|
plot.plot(
|
||||||
@@ -1551,7 +1651,7 @@ def main(csv_paths, *,
|
|||||||
s.xmargin[1], ''))
|
s.xmargin[1], ''))
|
||||||
|
|
||||||
# draw plot!
|
# draw plot!
|
||||||
f.write(s.plot_.draw(subrow, color=color))
|
f.write(s.plot_.draw(subrow))
|
||||||
|
|
||||||
# footer
|
# footer
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -321,6 +321,8 @@ class Canvas:
|
|||||||
else:
|
else:
|
||||||
xscale, yscale = 1, 1
|
xscale, yscale = 1, 1
|
||||||
|
|
||||||
|
self.width_ = width
|
||||||
|
self.height_ = height
|
||||||
self.width = xscale*width
|
self.width = xscale*width
|
||||||
self.height = yscale*height
|
self.height = yscale*height
|
||||||
self.xscale = xscale
|
self.xscale = xscale
|
||||||
@@ -330,50 +332,63 @@ class Canvas:
|
|||||||
self.braille = braille
|
self.braille = braille
|
||||||
|
|
||||||
# create initial canvas
|
# create initial canvas
|
||||||
self.grid = [False] * (self.width*self.height)
|
self.chars = [0] * (width*height)
|
||||||
self.colors = [''] * (self.width*self.height)
|
self.colors = [''] * (width*height)
|
||||||
|
|
||||||
def __getitem__(self, xy):
|
def char(self, x, y, char=None):
|
||||||
x, y = xy
|
|
||||||
# ignore out of bounds
|
# ignore out of bounds
|
||||||
if x < 0 or y < 0 or x >= self.width or y >= self.height:
|
if x < 0 or y < 0 or x >= self.width or y >= self.height:
|
||||||
return
|
return False
|
||||||
|
|
||||||
return self.grid[x + y*self.width]
|
x_ = x // self.xscale
|
||||||
|
y_ = y // self.yscale
|
||||||
def __setitem__(self, xy, char):
|
if char is not None:
|
||||||
x, y = xy
|
c = self.chars[x_ + y_*self.width_]
|
||||||
# ignore out of bounds
|
# mask in sub-char pixel?
|
||||||
if x < 0 or y < 0 or x >= self.width or y >= self.height:
|
if isinstance(char, bool):
|
||||||
return
|
if not isinstance(c, int):
|
||||||
|
c = 0
|
||||||
self.grid[x + y*self.width] = char
|
self.chars[x_ + y_*self.width_] = (c
|
||||||
|
| (1
|
||||||
|
<< ((y%self.yscale)*self.xscale
|
||||||
|
+ (self.xscale-1)-(x%self.xscale))))
|
||||||
|
else:
|
||||||
|
self.chars[x_ + y_*self.width_] = char
|
||||||
|
else:
|
||||||
|
c = self.chars[x_ + y_*self.width_]
|
||||||
|
if isinstance(c, int):
|
||||||
|
return ((c
|
||||||
|
>> ((y%self.yscale)*self.xscale
|
||||||
|
+ (self.xscale-1)-(x%self.xscale)))
|
||||||
|
& 1) == 1
|
||||||
|
else:
|
||||||
|
return c
|
||||||
|
|
||||||
def color(self, x, y, color=None):
|
def color(self, x, y, color=None):
|
||||||
# ignore out of bounds
|
# ignore out of bounds
|
||||||
if x < 0 or y < 0 or x >= self.width or y >= self.height:
|
if x < 0 or y < 0 or x >= self.width or y >= self.height:
|
||||||
return
|
return ''
|
||||||
|
|
||||||
|
x_ = x // self.xscale
|
||||||
|
y_ = y // self.yscale
|
||||||
if color is not None:
|
if color is not None:
|
||||||
self.colors[x + y*self.width] = color
|
self.colors[x_ + y_*self.width_] = color
|
||||||
else:
|
else:
|
||||||
return self.colors[x + y*self.width]
|
return self.colors[x_ + y_*self.width_]
|
||||||
|
|
||||||
|
def __getitem__(self, xy):
|
||||||
|
x, y = xy
|
||||||
|
return self.char(x, y)
|
||||||
|
|
||||||
|
def __setitem__(self, xy, char):
|
||||||
|
x, y = xy
|
||||||
|
self.char(x, y, char)
|
||||||
|
|
||||||
def point(self, x, y, *,
|
def point(self, x, y, *,
|
||||||
char=True,
|
char=True,
|
||||||
color=''):
|
color=''):
|
||||||
# make sure non-bool chars map attrs to all points under char
|
self.char(x, y, char)
|
||||||
if not isinstance(char, bool):
|
self.color(x, y, color)
|
||||||
xscale, yscale = self.xscale, self.yscale
|
|
||||||
else:
|
|
||||||
xscale, yscale = 1, 1
|
|
||||||
|
|
||||||
for i in range(xscale*yscale):
|
|
||||||
x_ = x-(x%xscale) + (xscale-1-(i%xscale))
|
|
||||||
y_ = y-(y%yscale) + (i//xscale)
|
|
||||||
|
|
||||||
self[x_, y_] = char
|
|
||||||
self.color(x_, y_, color)
|
|
||||||
|
|
||||||
def line(self, x1, y1, x2, y2, *,
|
def line(self, x1, y1, x2, y2, *,
|
||||||
char=True,
|
char=True,
|
||||||
@@ -386,7 +401,7 @@ class Canvas:
|
|||||||
e = ex + ey
|
e = ex + ey
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
self.point(x1, y1, color=color, char=char)
|
self.point(x1, y1, char=char, color=color)
|
||||||
e2 = 2*e
|
e2 = 2*e
|
||||||
|
|
||||||
if x1 == x2 and y1 == y2:
|
if x1 == x2 and y1 == y2:
|
||||||
@@ -403,7 +418,7 @@ class Canvas:
|
|||||||
e += ex
|
e += ex
|
||||||
y1 += dy
|
y1 += dy
|
||||||
|
|
||||||
self.point(x2, y2, color=color, char=char)
|
self.point(x2, y2, char=char, color=color)
|
||||||
|
|
||||||
def rect(self, x, y, w, h, *,
|
def rect(self, x, y, w, h, *,
|
||||||
char=True,
|
char=True,
|
||||||
@@ -427,47 +442,29 @@ class Canvas:
|
|||||||
x_ += self.xscale
|
x_ += self.xscale
|
||||||
|
|
||||||
def draw(self, row):
|
def draw(self, row):
|
||||||
# scale if needed
|
y_ = self.height_-1 - row
|
||||||
xscale, yscale = self.xscale, self.yscale
|
|
||||||
|
|
||||||
y = self.height//yscale-1 - row
|
|
||||||
row_ = []
|
row_ = []
|
||||||
for x in range(self.width//xscale):
|
for x_ in range(self.width_):
|
||||||
color = ''
|
# char?
|
||||||
char = False
|
c = self.chars[x_ + y_*self.width_]
|
||||||
byte = 0
|
if isinstance(c, int):
|
||||||
for i in range(xscale*yscale):
|
if self.braille:
|
||||||
x_ = x*xscale + (xscale-1-(i%xscale))
|
assert c < 256
|
||||||
y_ = y*yscale + (i//xscale)
|
c = CHARS_BRAILLE[c]
|
||||||
|
elif self.dots:
|
||||||
# calculate char
|
assert c < 4
|
||||||
char_ = self[x_, y_]
|
c = CHARS_DOTS[c]
|
||||||
if char_:
|
|
||||||
byte |= 1 << i
|
|
||||||
if char_ is not True and char_ is not False:
|
|
||||||
char = char_
|
|
||||||
|
|
||||||
# keep track of best color
|
|
||||||
color_ = self.color(x_, y_)
|
|
||||||
if color_:
|
|
||||||
color = color_
|
|
||||||
|
|
||||||
# figure out winning char
|
|
||||||
if byte:
|
|
||||||
if char is not True and char is not False:
|
|
||||||
pass
|
|
||||||
elif self.braille:
|
|
||||||
char = CHARS_BRAILLE[byte]
|
|
||||||
else:
|
else:
|
||||||
char = CHARS_DOTS[byte]
|
assert c < 2
|
||||||
else:
|
c = '.' if c else ' '
|
||||||
char = ' '
|
|
||||||
|
|
||||||
# color?
|
# color?
|
||||||
if byte and self.color_ and color:
|
if self.color_:
|
||||||
char = '\x1b[%sm%s\x1b[m' % (color, char)
|
color = self.colors[x_ + y_*self.width_]
|
||||||
|
if color:
|
||||||
|
c = '\x1b[%sm%s\x1b[m' % (color, c)
|
||||||
|
|
||||||
row_.append(char)
|
row_.append(c)
|
||||||
|
|
||||||
return ''.join(row_)
|
return ''.join(row_)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user