I'm trying to get it to plot a graph, but right now it doesn't really seem to output the correct values for a function. I'm pretty sure the problem lies in either the scale_to_idx
or scale_to_range
function but I'm stumped with regards to fixing it.
The code is supposed to take a simple expression like x**2
for example and 'draw' a graph for the specified x and y values.
class Plotter():
def __init__(self, size, xrange, yrange):
self.size = size
self.xrange = xrange
self.yrange = yrange
self.refresh()
def refresh(self):
self.pixels = [[" " for i in range(self.size)] for i in range(self.size)]
#Draw Left Boundary
for y in range(self.size):
self.pixels[y][0] = "*"
#Draw Bottom Boundary
for x in range(self.size):
self.pixels[0][x] = '**'
def display(self):
for row in self.pixels:
print("".join(row))
def scale_to_idx(self, value, value_range): ##smth wrong with scale
rel = (int(value) - int(value_range[0]))/(int(value_range[0]) - int(value_range[-1]))
print("(int(value_range[0]) - int(value_range[-1]))", (int(value_range[0]) - int(value_range[-1])))
print("int(value) - int(value_range[0]))", (int(value) - int(value_range[0])))
idx = rel * self.size
return idx
def scale_to_range(self, idx, value_range): ##smth wrong with scale
rel = idx / self.size
value = int(value_range[0]) rel * (int(value_range[-1]) - int(value_range[0]))
return value
def plot(self, f, plot_char):
for x_idx in range(self.size):
x = self.scale_to_range(x_idx, self.xrange)
y = f(x_idx)
if y > int(self.yrange[0]) or y < int(self.yrange[1]):
y_idx = int(self.scale_to_idx(x, self.yrange))
self.pixels[y_idx][x_idx] = plot_char
def validate_range(value_range):
if len(value_range) != 2:
print("Enter two values separated by a space! Try again...")
return False
if value_range[0] >= value_range[1]:
print("Lower end of range must be lower than higher end! Try again...")
return False
return True
if __name__ == "__main__":
size = int(input("Enter plot size (Press [ENTER] for default) ") or 50)
while True:
xrange = input("Enter X-axis range seperated by space (default is '-10 10'): ") or "-10 10"
yrange = input("Enter Y-axis range seperated by space (default is '-10 10'): ") or "-10 10"
xrange = xrange.split()
yrange = yrange.split()
if validate_range(xrange) and validate_range(yrange):
break
plotter = Plotter(size, xrange, yrange)
while True:
answer = input("Do you wish to add (another) function? [y/n]: ")
if answer == "y":
func_string = input("Input function code with x as variable (e.g. 'x**2'): ")
if not func_string:
break
plot_char = input("Set Plotting Character: ") or "--"
try:
exec(f"def f(x): return {func_string}")
plotter.plot(f, plot_char)
except SyntaxError:
print("Invalid Function Expression! Try again...")
elif answer == "n":
break
plotter.display()
CodePudding user response:
Cool idea. I have seen a few other ideas floating out there about this.
Your code seems to work but I didn't work through exactly what was broken with your implementation since it was just a little hard to see. However, when doing the conversions correctly, everything seems to work for me. See the revised code below. The changes are in converting your range inputs to floats, plotting the rows in reversed order so that the plot looks right, and lastly, changing the logic for calculating the x,y pixel indices. I commented out your code that is no longer needed
here is a result:
class Plotter():
def __init__(self, size, xrange, yrange):
self.size = size
self.xrange = xrange
self.yrange = yrange
self.refresh()
def refresh(self):
self.pixels = [[" " for i in range(self.size)] for i in range(self.size)]
#Draw Left Boundary
for y in range(self.size):
self.pixels[y][0] = "*"
#Draw Bottom Boundary
for x in range(self.size):
self.pixels[0][x] = '**'
def display(self):
print(self.size)
print(self.xrange)
print(self.yrange)
# if you want the plot to look right, you need to draw in reverse order
for row in reversed(self.pixels):
print("".join(row))
# def scale_to_idx(self, value, value_range): ##smth wrong with scale
# rel = (int(value) - int(value_range[0]))/(int(value_range[0]) - int(value_range[-1]))
# # print("(int(value_range[0]) - int(value_range[-1]))", (int(value_range[0]) - int(value_range[-1])))
# # print("int(value) - int(value_range[0]))", (int(value) - int(value_range[0])))
# idx = rel * self.size
# return idx
# def scale_to_range(self, idx, value_range): ##smth wrong with scale
# rel = idx / self.size
# value = int(value_range[0]) rel * (int(value_range[-1]) - int(value_range[0]))
# return value
def plot(self, f, plot_char):
xmin, xmax = self.xrange
ymin, ymax = self.yrange
size = self.size
# we know what the xidxs are and the xvals will just be a linspace type operation
xidx = list(range(size))
xstep = (xmax - xmin) / (size - 1)
xvals = [xmin xstep * i for i in xidx]
# the yvals we can create just with the function call
yvals = [f(x) for x in xvals]
# basically the relationship between the yscale and the ypixel index is a line
m = (size - 1) / (ymax - ymin)
b = -m * ymin
# convert to the nearest integer value
yidx = [int(round(m * x b)) for x in yvals]
for x, y, x_idx, y_idx in zip(xvals, yvals, xidx, yidx):
if y_idx >= 0 and y_idx < size:
try:
self.pixels[y_idx][x_idx] = plot_char
except Exception as e:
print(x, y, x_idx, y_idx)
raise e
# for x_idx in range(self.size):
# x = self.scale_to_range(x_idx, self.xrange)
# # y = f(x_idx)
# y = f(x)
# if y > int(self.yrange[0]) or y < int(self.yrange[1]):
# y_idx = int(self.scale_to_idx(x, self.yrange))
# self.pixels[y_idx][x_idx] = plot_char
def validate_range(value_range):
if len(value_range) != 2:
print("Enter two values separated by a space! Try again...")
return False
if value_range[0] >= value_range[1]:
print("Lower end of range must be lower than higher end! Try again...")
return False
return True
if __name__ == "__main__":
size = int(input("Enter plot size (Press [ENTER] for default) ") or 50)
while True:
xrange = input("Enter X-axis range seperated by space (default is '-10 10'): ") or "-10 10"
yrange = input("Enter Y-axis range seperated by space (default is '-10 10'): ") or "-10 10"
xrange = list(map(float, xrange.split()))
yrange = list(map(float, yrange.split()))
if validate_range(xrange) and validate_range(yrange):
break
plotter = Plotter(size, xrange, yrange)
while True:
answer = input("Do you wish to add (another) function? [y/n]: ")
if answer == "y":
func_string = input("Input function code with x as variable (e.g. 'x**2'): ")
if not func_string:
break
plot_char = input("Set Plotting Character: ") or "--"
try:
exec(f"def f(x): return {func_string}")
plotter.plot(f, plot_char)
except SyntaxError:
print("Invalid Function Expression! Try again...")
elif answer == "n":
break
plotter.display()