From 05f2ddd9b3ee86e5e28f6c51043ee8f51cf25204 Mon Sep 17 00:00:00 2001 From: Chris Proctor Date: Sun, 30 Jul 2023 17:30:53 -0400 Subject: [PATCH] Udpated --- constants.py | 1 + movement.py | 6 --- plotting.py | 48 ++++++++++++++++++ scatterplot.py | 121 +++++++++++----------------------------------- test_transform.py | 34 +++++++++++++ ticks.py | 26 ++++++++++ transform.py | 45 +++++++++++++++++ 7 files changed, 181 insertions(+), 100 deletions(-) delete mode 100644 movement.py create mode 100644 plotting.py create mode 100644 test_transform.py create mode 100644 ticks.py create mode 100644 transform.py diff --git a/constants.py b/constants.py index 0a79614..d77003b 100644 --- a/constants.py +++ b/constants.py @@ -1,3 +1,4 @@ PLOT_HEIGHT = 400 PLOT_WIDTH = 400 +PLOT_PADDING = 20 TICK_LENGTH = 5 diff --git a/movement.py b/movement.py deleted file mode 100644 index 7780c54..0000000 --- a/movement.py +++ /dev/null @@ -1,6 +0,0 @@ -from turtle import * - -def flyto(x, y): - penup() - goto(x, y) - pendown() diff --git a/plotting.py b/plotting.py new file mode 100644 index 0000000..0ede5ff --- /dev/null +++ b/plotting.py @@ -0,0 +1,48 @@ +# plotting.py +# ------------ +# By MWC Contributors +# The functions in this module draw parts of a scatter plot. +# These functions are all complete; you don't need to edit this file. + +from turtle import * +import constants + +def flyto(x, y): + penup() + goto(x, y) + pendown() + +def prepare_screen(): + """Sets up the screen for a plot. + """ + screensize(constants.PLOT_WIDTH, constants.PLOT_HEIGHT) + setworldcoordinates( + -constants.PLOT_PADDING, + -constants.PLOT_PADDING, + constants.PLOT_WIDTH + constants.PLOT_PADDING, + constants.PLOT_HEIGHT + constants.PLOT_PADDING, + ) + +def draw_point(x, y, color, size): + "Draws a dot at (x, y) screen position, using the color and size provided." + flyto(x, y) + dot(size, color) + +def draw_x_axis(): + flyto(0, 0) + goto(constants.PLOT_WIDTH, 0) + +def draw_y_axis(): + flyto(0, 0) + goto(0, constants.PLOT_HEIGHT) + +def draw_y_tick(position, label): + flyto(0, position) + goto(-constants.TICK_LENGTH, position) + write(label, align='right') + +def draw_x_tick(position, label): + flyto(position, 0) + goto(position, -constants.TICK_LENGTH) + flyto(position, -constants.TICK_LENGTH - 10) + write(label, align='center') diff --git a/scatterplot.py b/scatterplot.py index 011edb1..50f66fb 100644 --- a/scatterplot.py +++ b/scatterplot.py @@ -1,22 +1,31 @@ -# What needs to be taught? -# - return values -# - unpacking multiple return values -# - list comprehensions -# - named function arguments -# - after positional -# - like dictionaries -# - -# - modules - -# Parameters: -# - the actual drawing will be +# scatterplot.py +# ------------ +# By MWC Contributors +# Uses lots of helper functions in other modules to draw a scatter plot. from turtle import * -from math import floor, ceil, log -import constants -from movement import flyto from superturtle.movement import no_delay +import constants from generate_data import generate_data +from ticks import get_tick_values +from plotting import ( + prepare_screen, + draw_x_axis, + draw_y_axis, + draw_x_tick, + draw_y_tick, + draw_point, +) +from transform import ( + maximum, + minimum, + bounds, + clamp, + ratio, + scale, + get_x_values, + get_y_values, +) def draw_scatterplot(data, size=5, color="black"): "Draws a scatter plot, showing the data" @@ -24,90 +33,14 @@ def draw_scatterplot(data, size=5, color="black"): draw_axes(data) draw_points(data, color, size) -def prepare_screen(): - """Sets up the - """ - screensize(400, 400) - setworldcoordinates(-20, -20, 420, 420) - def draw_axes(data): - """Draws the scatter plot's axes. - """ - flyto(0, 0) - goto(0, constants.PLOT_HEIGHT) - flyto(0, 0) - goto(constants.PLOT_WIDTH, 0) - - y_values = get_y_values(data) - ymin, ymax = get_bounds(y_values) - draw_y_tick(0, ymin) - draw_y_tick(constants.PLOT_HEIGHT, ymax) - for tick in get_tick_range(y_values): - position = scale(tick, ymin, ymax, 0, constants.PLOT_HEIGHT) - draw_y_tick(position, tick) - - x_values = get_x_values(data) - xmin, xmax = get_bounds(x_values) - draw_x_tick(0, xmin) - draw_x_tick(constants.PLOT_WIDTH, xmax) - for tick in get_tick_range(x_values): - position = scale(tick, xmin, xmax, 0, constants.PLOT_WIDTH) - draw_x_tick(position, tick) - -def get_tick_range(values): - "Returns a range of positions for ticks" - vmin, vmax = get_bounds(values) - tick_interval = get_tick_interval(values) - first_tick = ceil(vmin / tick_interval) * tick_interval - print(tick_interval) - print(first_tick) - print(vmax) - return range(first_tick, vmax, tick_interval) - -def get_tick_interval(values): - "" - vmin, vmax = get_bounds(values) - log_span = log(vmax - vmin, 10) - return 10 ** floor(log_span) + "Draws the scatter plot's axes." def draw_points(data, color, size): - xmin, xmax = get_bounds(get_x_values(data)) - ymin, ymax = get_bounds(get_y_values(data)) - for x, y in data: - sx = scale(x, xmin, xmax, 0, constants.PLOT_WIDTH) - sy = scale(y, ymin, ymax, 0, constants.PLOT_HEIGHT) - draw_point(sx, sy, color, size) - -def draw_point(x, y, color, size): - flyto(x, y) - dot(size, color) - -def get_bounds(data): - return min(data), max(data) - -def get_x_values(data): - return [x for x, y, in data] - -def get_y_values(data): - return [y for x, y in data] - -def draw_y_tick(position, label): - flyto(0, position) - goto(-constants.TICK_LENGTH, position) - write(label, align='right') - -def draw_x_tick(position, label): - flyto(position, 0) - goto(position, -constants.TICK_LENGTH) - flyto(position, -constants.TICK_LENGTH - 10) - write(label, align='center') - -def scale(value, domain_min, domain_max, range_min, range_max): - ratio = (value - domain_min) / (domain_max - domain_min) - return range_min + ratio * (range_max - range_min) + "Draws the scatter plot's points." with no_delay(): data = generate_data(50, 10, 500, 5, 400, 1000) draw_scatterplot(data, size=5, color="blue") hideturtle() -input() +done() diff --git a/test_transform.py b/test_transform.py new file mode 100644 index 0000000..2ced4a6 --- /dev/null +++ b/test_transform.py @@ -0,0 +1,34 @@ +from transform import ( + maximum, + minimum, + bounds, + clamp, + ratio, + scale, + get_x_values, + get_y_values, +) + +def test(function, arguments, expected): + observed = function(*arguments) + if observed != expected: + args = ', '.join(str(arg) for arg in arguments) + print(f"Error: Expected {function}({args}) to equal {expected}, but it was {observed}") + +test(maximum, [[0, 1, 2, 3]], 3) +test(maximum, [[-10, -20, -30]], -10) +test(minimum, [[0, 1, 2, 3]], 0) +test(minimum, [[-10, -20, -30]], -30) +test(bounds, [[0, 1, 2, 3]], [0, 3]) +test(bounds, [[-10, -20, -30]], [-30, -10]) +test(clamp, [[10, 0, 100]], 10) +test(clamp, [[-10, 0, 100]], 0) +test(clamp, [[104, 0, 100]], 100) +test(ratio, [[5, 0, 10]], 0.5) +test(ratio, [[167, 100, 200]], 0.67) +test(ratio, [[8, 10, 0]], 0.2) +test(ratio, [[4, 10, 20]], 0.0) +test(scale, [[4, 0, 10, 0, 100]], 40) +test(scale, [[160, 120, 240, 0, 100]], 100/3) +test(get_x_values, [[[0, 5], [1, 5], [2, 5]]], [0, 1, 2]) +test(get_y_values, [[[0, 5], [1, 5], [2, 5]]], [5, 5, 5]) diff --git a/ticks.py b/ticks.py new file mode 100644 index 0000000..7c38194 --- /dev/null +++ b/ticks.py @@ -0,0 +1,26 @@ +# ticks.py +# ------------ +# By MWC Contributors +# The functions in this module calculate suitable placements +# for ticks on a plot axis which will display values. +# These functions are all complete; you don't need to edit this file. + +from math import floor, ceil, log +from transform import bounds + +def get_tick_values(low, high): + """Returns a list of values to use for ticks (labeled points along an axis). + Includes the lowest value, a bunch of "nice" intermediate values, and the highest value. + """ + tick_interval = get_tick_interval(high - low) + first_tick = ceil(low / tick_interval) * tick_interval + return [low] + list(range(first_tick, high, tick_interval)) + [high] + +def get_tick_interval(span): + """Returns a 'nice' interval for ticks across span. + The interval is a power of ten (e.g. 1000, 100, 0.1) + scaled so that there will be between 0 and 10 internal ticks. + """ + log_span = log(span, 10) + return 10 ** floor(log_span) + diff --git a/transform.py b/transform.py new file mode 100644 index 0000000..38e6c71 --- /dev/null +++ b/transform.py @@ -0,0 +1,45 @@ +# transform.py +# ------------ +# By MWC Contributors +# The functions in this module transform data. +# None of them are finished; this is your job! + +def maximum(data): + "Returns the largest number in data" + raise NotImplementedError + +def minimum(data): + "Returns the smallest number in data" + raise NotImplementedError + +def bounds(data): + "Returns a list of the largest and smallest numbers in data" + raise NotImplementedError + +def clamp(value, low, high): + """Clamps a value to a range from low to high. + Returns value if it is between low and high. + If value is lower than low, returns low. If value is higher than high, returns high. + """ + raise NotImplementedError + +def ratio(value, start, end): + """Returns a number from 0.0 to 1.0, representing how far along value is from start to end. + The return value is clamped to [0, 1], so even if value is lower than start, the return + value will not be lower than 0.0. + """ + raise NotImplementedError + +def scale(value, domain_min, domain_max, range_min, range_max): + "Given a value within a domain, returns the scaled equivalent within range." + ratio = (value - domain_min) / (domain_max - domain_min) + return range_min + ratio * (range_max - range_min) + raise NotImplementedError + +def get_x_values(points): + "Returns the first value for each point in points." + raise NotImplementedError + +def get_y_values(points): + "Returns the second value for each point in points." + raise NotImplementedError