commit fdb66d683b918e5d69a17eb3a5c8378de49f7377 Author: hope Date: Fri Apr 25 21:27:24 2025 +0000 Initial commit diff --git a/.commit_template b/.commit_template new file mode 100644 index 0000000..d7a77d1 --- /dev/null +++ b/.commit_template @@ -0,0 +1,25 @@ + + +# ----------------------------------------------------------------- +# Write your entire commit message above this line. +# +# The first line should be a quick description of what you changed. +# Then leave a blank line. +# Then, taking as many lines as you want, answer the questions for +# your current checkpoint: +# +# Checkpoint 1: How much of the scatter plot do you think you could +# write on your own right now? While you were planning, did you come +# across any specific issues which you would not know how to program +# yet? +# +# Checkpoint 2: In this checkpoint, you were asked to write some more +# complex functions than you may have written before. How did it go? +# Did it feel like a different kind of thinking? When you got stuck, +# what strategies did you use to make progress? +# +# Checkpoint 3: This may have been the most complex program you have +# yet written. How did it go? Did you understand how the parts came +# together? Compare the top-down thinking you did in Checkpoint 1 with +# the way this lab broke down the scatter plot program. Did you have +# any ideas for other programs you could write? diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96403d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/* diff --git a/constants.py b/constants.py new file mode 100644 index 0000000..d77003b --- /dev/null +++ b/constants.py @@ -0,0 +1,4 @@ +PLOT_HEIGHT = 400 +PLOT_WIDTH = 400 +PLOT_PADDING = 20 +TICK_LENGTH = 5 diff --git a/generate_data.py b/generate_data.py new file mode 100644 index 0000000..4432013 --- /dev/null +++ b/generate_data.py @@ -0,0 +1,9 @@ +from random import randint, gauss + +def generate_data(n, xmin, xmax, slope, intercept, error): + sample = [] + for i in range(n): + x = randint(xmin, xmax) + y = round(gauss(intercept + x * slope, error)) + sample.append([x, y]) + return sample diff --git a/planning_scatter.md b/planning_scatter.md new file mode 100644 index 0000000..d566f3f --- /dev/null +++ b/planning_scatter.md @@ -0,0 +1,12 @@ +# Planning the scatter plot + +- Draw a scatter plot. + - Draw the axes. + - Draw the x-axis. + - Draw the line. + - ... + - Draw the y-axis. + - Draw the line. + - ... + - Plot the points. + - ... 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/poetry.lock b/poetry.lock new file mode 100644 index 0000000..193eda8 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,18 @@ +# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand. + +[[package]] +name = "superturtle" +version = "0.2.0" +description = "Extensions to Python's turtle" +optional = false +python-versions = "<4.0,>=3.9" +groups = ["main"] +files = [ + {file = "superturtle-0.2.0-py3-none-any.whl", hash = "sha256:ca3a31be3259387b4490846adbf64502acc9d23472912cc43497ab170e89f506"}, + {file = "superturtle-0.2.0.tar.gz", hash = "sha256:807fb419c1dba9cb809a22a68e72c0193bdeed4a9326eb36ad940b2a7ff6ac04"}, +] + +[metadata] +lock-version = "2.1" +python-versions = ">=3.10,<4.0" +content-hash = "6aad436bbbf760fa856344262eab22d62a167cac4e5dfefbf4be77d5a37428c9" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..553ee56 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,21 @@ +[project] +name = "lab-scatter" +version = "0.1.0" +description = "" +license = {text = "MIT"} +authors = [ + {name = "Chris Proctor",email = "chris@chrisproctor.net"} +] +readme = "README.md" +requires-python = ">=3.10,<4.0" +dependencies = [ + "superturtle (>=0.2.0,<0.3.0)" +] + + +[build-system] +requires = ["poetry-core>=2.0.0,<3.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry] +package-mode = false diff --git a/scatterplot.py b/scatterplot.py new file mode 100644 index 0000000..50f66fb --- /dev/null +++ b/scatterplot.py @@ -0,0 +1,46 @@ +# scatterplot.py +# ------------ +# By MWC Contributors +# Uses lots of helper functions in other modules to draw a scatter plot. + +from turtle import * +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" + prepare_screen() + draw_axes(data) + draw_points(data, color, size) + +def draw_axes(data): + "Draws the scatter plot's axes." + +def draw_points(data, color, size): + "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() +done() diff --git a/test_transform.py b/test_transform.py new file mode 100644 index 0000000..4a1e7da --- /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(minimum, [[0, 1, 2, 3]], 0) +test(minimum, [[-10, -20, -30]], -30) +test(maximum, [[0, 1, 2, 3]], 3) +test(maximum, [[-10, -20, -30]], -10) +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, [180, 120, 240, 0, 100], 50) +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..64819b7 --- /dev/null +++ b/transform.py @@ -0,0 +1,43 @@ +# 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 smallest and largest 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." + 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