From 86c84e29dc8db2eef474d1fc692487481a741d3e Mon Sep 17 00:00:00 2001 From: Dylan Tintenfich Date: Fri, 30 Dec 2022 00:15:36 -0300 Subject: [PATCH 1/4] Add ScreenConfiguration class Along the creation of this new class, a few updates to curvipy._plotter and curvipy._screen modules were made. 'curvipy.ScreenFacade' modifications: - Fixed 'get_screen_size()' method - Added 'get_real_point()' method - Added 'goto_drawing()' method - Added 'goto_without_drawing()' method - Now 'curvipy.ScreenFacade' can handle virtual positions (see documentation) 'curvipy.Plotter' modifications: - Plotter now works with virtual coordinates - Updated '_draw_axis()' - Updated 'draw_x_tick()' - Updated 'draw_y_ticks()' --- curvipy/_plotter.py | 246 +++++++++++++++++++++++--------------------- curvipy/_screen.py | 233 ++++++++++++++++++++++++++++++----------- 2 files changed, 297 insertions(+), 182 deletions(-) diff --git a/curvipy/_plotter.py b/curvipy/_plotter.py index 7b6c451..decc227 100644 --- a/curvipy/_plotter.py +++ b/curvipy/_plotter.py @@ -10,6 +10,48 @@ _TNumber = _Union[int, float] +class ScreenConfiguration: + """Defines the configuration for the plotter screen. + + Parameters + ---------- + window_title : str + Title to display on window. Defaults to "Curvipy". + background_color : str + Background color. Can either be a name or a hex color code. Defaults to "#FFFFFF". + window_width : int or None + Width of the screen window (in pixels). If None, `window_width` equals to 50% of the \ + display width. Defaults to None. + window_height : int or None + Height of the screen window (in pixels). If None, `window_height` equals to 75% of \ + the display height. Defaults to None. + logical_width : int + Logical width of the screen. This is the width that `Plotter` will operate with. + While `window_width` is the real width of the screen, `logical_width` is a virtual \ + representation of it. + logical_height : int + Logical height of the screen. This is the height that `Plotter` will operate with. + While `window_height` is the real height of the screen, `logical_height` is a virtual \ + representation of it. + """ + + def __init__( + self, + window_title: str = "Curvipy", + background_color: str = "#FFFFFF", + window_width: _Union[int, None] = None, + window_height: int = None, + logical_width: int = 20, + logical_height: int = 20, + ): + self.window_title = window_title + self.background_color = background_color + self.window_width = window_width + self.window_height = window_height + self.logical_width = logical_width + self.logical_height = logical_height + + class PlottingConfiguration: """Defines the configuration for plotting curves and vectors. @@ -100,8 +142,6 @@ class AxesConfiguration: to the left or to the right of the x-axis. Defaults to "left". """ - __AXES_SCALE_FACTOR = 30 - def __init__( self, show_axes: bool = True, @@ -121,8 +161,8 @@ def __init__( self.show_axes = show_axes self.axes_color = axes_color self.axes_width = axes_width - self.x_axis_scale = x_axis_scale * __class__.__AXES_SCALE_FACTOR - self.y_axis_scale = y_axis_scale * __class__.__AXES_SCALE_FACTOR + self.x_axis_scale = x_axis_scale + self.y_axis_scale = y_axis_scale self.x_axis_ticks = x_axis_ticks self.y_axis_ticks = y_axis_ticks self.x_axis_tick_decimals = x_axis_tick_decimals @@ -134,51 +174,62 @@ def __init__( class Plotter: - """Graph plotter for drawing vectors and curves. + """Graph plotter for drawing curves and vectors. Parameters ---------- - window_title : str - Title to display on window. Defaults to "Curvipy". - background_color : str - Background color. Can either be a name or a hex color code. Defaults to "#FFFFFF". + screen_config : ScreenConfiguration + Screen configuration. By default, `screen_config` takes the defaults attributes \ + values of `ScreenConfiguration`. plotting_config : PlottingConfiguration - Plotting configuration. If None, `plotting_config` takes the defaults attributes \ + Plotting configuration. By default, `plotting_config` takes the defaults attributes \ values of `PlottingConfiguration`. axes_config : AxesConfiguration - Axes configuration. If None, `axes_config` takes the defaults attributes values \ + Axes configuration. By default, `axes_config` takes the defaults attributes values \ of `AxesConfiguration`. """ def __init__( self, - window_title: str = "Curvipy", - background_color: str = "#FFFFFF", + screen_config: ScreenConfiguration = None, plotting_config: PlottingConfiguration = None, axes_config: AxesConfiguration = None, ) -> None: - # Screen attributes - self.__screen = _ScreenFacade(window_title, background_color) - self.plotting_config = ( - plotting_config if plotting_config is not None else PlottingConfiguration() - ) - self.axes_config = ( - axes_config if axes_config is not None else AxesConfiguration() + if screen_config is not None: + self.screen_config = screen_config + else: + self.screen_config = ScreenConfiguration() + + if plotting_config is not None: + self.plotting_config = plotting_config + else: + self.plotting_config = PlottingConfiguration() + + if axes_config is not None: + self.axes_config = axes_config + else: + self.axes_config = AxesConfiguration() + + self.__screen = _ScreenFacade( + self.screen_config.window_title, + self.screen_config.background_color, + self.screen_config.window_width, + self.screen_config.window_height, + self.screen_config.logical_width, + self.screen_config.logical_height, ) if self.axes_config.show_axes: self._draw_axis() def _draw_axis(self) -> None: - screen_size = self.__screen.get_screen_size() - w, h = (screen_size[0] * 1.5, screen_size[1] * 1.5) - axis_extension = 20 # for drawing the axis arrow + w, h = self.screen_config.logical_width, self.screen_config.logical_height # Y-AXIS ## Draw y-axis line self.__screen.draw_line( - (0, -h - axis_extension), - (0, h + axis_extension), + (0, -h / 2), + (0, h / 2), self.axes_config.axes_width, self.axes_config.axes_color, self.__screen.MAX_DRAWING_SPEED, @@ -188,9 +239,15 @@ def _draw_axis(self) -> None: dy = ( 0 if not self.axes_config.y_axis_ticks - else _ceil(h / self.axes_config.y_axis_ticks) + else h / (self.axes_config.y_axis_ticks * 2) ) - for i in range(self.axes_config.y_axis_ticks * 2 + 1): + + if self.axes_config.y_axis_scale > 0: + i_range = range(self.axes_config.y_axis_ticks * 2) + else: + i_range = range(self.axes_config.y_axis_ticks * 2, 0, -1) + + for i in i_range: y = dy * (i - self.axes_config.y_axis_ticks) if y == 0: continue @@ -198,9 +255,7 @@ def _draw_axis(self) -> None: ## Draw y-axis line arrow axis_arrow_pos = ( - (0, h + axis_extension) - if self.axes_config.y_axis_scale > 0 - else (0, -h - axis_extension) + (0, h / 2) if self.axes_config.y_axis_scale > 0 else (0, -h / 2) ) axis_arrow_ang = 0.5 * _pi if self.axes_config.y_axis_scale > 0 else -0.5 * _pi self.__screen.draw_arrow( @@ -215,8 +270,8 @@ def _draw_axis(self) -> None: # X-AXIS ## Draw x-axis line self.__screen.draw_line( - (-w - axis_extension, 0), - (w + axis_extension, 0), + (-w / 2, 0), + (w / 2, 0), self.axes_config.axes_width, self.axes_config.axes_color, self.__screen.MAX_DRAWING_SPEED, @@ -226,23 +281,25 @@ def _draw_axis(self) -> None: dx = ( 0 if not self.axes_config.x_axis_ticks - else _ceil(w / self.axes_config.x_axis_ticks) + else w / (self.axes_config.x_axis_ticks * 2) ) - for i in range(self.axes_config.x_axis_ticks * 2 + 1): + + if self.axes_config.x_axis_scale > 0: + i_range = range(self.axes_config.x_axis_ticks * 2) + else: + i_range = range(self.axes_config.x_axis_ticks * 2, 0, -1) + + for i in i_range: x = dx * (i - self.axes_config.x_axis_ticks) if x == 0: continue self.draw_x_tick(x / self.axes_config.x_axis_scale) ## Draw x-axis line arrow - axis_arrow_pos = ( - (w + axis_extension, 0) - if self.axes_config.x_axis_scale > 0 - else (-w - axis_extension, 0) - ) + point = (w / 2, 0) if self.axes_config.x_axis_scale > 0 else (-w / 2, 0) axis_arrow_ang = 0 if self.axes_config.x_axis_scale > 0 else _pi self.__screen.draw_arrow( - point=axis_arrow_pos, + point=point, arrow_angle=axis_arrow_ang, arrow_size=10, arrow_width=self.axes_config.axes_width, @@ -262,30 +319,38 @@ def draw_x_tick(self, number: _TNumber, align: str = None) -> None: upside or downside the y-axis. If `None`, align takes \ `Plotter.x_axis_tick_number_align` attribute value. Defaults to `None`. """ + # Values for `y_offset`, `text_offset` and `tick_length` have been determined + # experimentally. This method work properly for small values of text font size + # and well spaced ticks. If the given font is too big or ticks are drawn relative + # closely, this method might overlap the ticks with their self or with the axes. + + # Variables setup + w, h = self.screen_config.logical_width, self.screen_config.logical_height font_size = self.axes_config.tick_number_font[1] number_str = str(round(number, self.axes_config.x_axis_tick_decimals)) # Offsets align = self.axes_config.x_axis_tick_number_align if not align else align if align == "down": - y_offset = -20 + y_offset = -0.04 * h # -4% of the y-axis length if align == "up": - y_offset = 10 + y_offset = 0.02 * h # 2% of the y-axis length - x_offset = -font_size * len(number_str) // 2 + text_offset = -font_size * len(number_str) * w * 0.5e-3 # Draw x tick + tick_length = 0.01 * h # 1% of the y-axis length x = number * self.axes_config.x_axis_scale self.__screen.draw_line( - (x, -5), - (x, 5), + (x, -tick_length), + (x, tick_length), self.axes_config.axes_width, self.axes_config.axes_color, self.__screen.MAX_DRAWING_SPEED, ) self.__screen.draw_text( text=number_str, - point=(x + x_offset, 0 + y_offset), + point=(x + text_offset, 0 + y_offset), text_font=self.axes_config.tick_number_font, text_color=self.axes_config.tick_number_color, align="left", @@ -303,23 +368,33 @@ def draw_y_tick(self, number: _TNumber, align: str = None) -> None: to the left or to the right of the x-axis. If `None`, align takes \ `Plotter.y_axis_tick_number_align` attribute value. Defaults to `None`. """ + # Values for `y_offset`, `x_offset`, `tick_length` and `tick_length` have been + # determined experimentally. This method work properly for small values of text + # font size and well spaced ticks. If the given font is too big or ticks are drawn + # relative closely, this method might overlap the ticks with their self or with the + # axes. + + # Variables setup + w, h = self.screen_config.logical_width, self.screen_config.logical_height font_size = self.axes_config.tick_number_font[1] number_str = str(round(number, self.axes_config.y_axis_tick_decimals)) # Offsets align = self.axes_config.y_axis_tick_number_align if not align else align if align == "left": - x_offset = -10 - font_size * len(number_str) * 0.8 + text_offset = font_size * len(number_str) * w * 1e-3 + x_offset = -0.02 * w - text_offset # 2% of x-axis length plus text offset if align == "right": - x_offset = 10 + x_offset = 0.02 * w # 2% of x-axis length - y_offset = -font_size // 2 + y_offset = -font_size * h * 1e-3 # Draw y tick + tick_length = 0.01 * w # 1% of the x-axis length y = number * self.axes_config.y_axis_scale self.__screen.draw_line( - (-5, y), - (5, y), + (-tick_length, y), + (tick_length, y), self.axes_config.axes_width, self.axes_config.axes_color, self.__screen.MAX_DRAWING_SPEED, @@ -374,75 +449,6 @@ def plot_vector(self, vector: _Vector) -> None: drawing_speed=self.__screen.MAX_DRAWING_SPEED, ) - # Show vector values - if not self.plotting_config.show_vector_values: - return - dash_size = 2 - - # Show vector tail values - if scaled_tail[0] != 0: - offset = 15 if scaled_tail[0] > 0 else -15 - total_dashes = int(abs(scaled_tail[0] // 20)) - self.__screen.draw_dashed_line( - (0, scaled_tail[1]), - (scaled_tail[0] - offset, scaled_tail[1]), - total_dashes, - dash_size, - self.plotting_config.vector_values_line_width, - self.plotting_config.vector_values_line_color, - self.plotting_config.plotting_speed, - ) # Horizontal dashed line - - offset = 15 if scaled_tail[1] > 0 else -15 - total_dashes = int(abs(scaled_tail[1] // 20)) - self.__screen.draw_dashed_line( - (scaled_tail[0], scaled_tail[1] - offset), - (scaled_tail[0], 0), - total_dashes, - dash_size, - self.plotting_config.vector_values_line_width, - self.plotting_config.vector_values_line_color, - self.plotting_config.plotting_speed, - ) # Vertical dashed line - - if scaled_tail[0] != 0: - align = "up" if vector.tail[0] < 0 else "down" - self.draw_x_tick(vector.tail[0], align) - align = "left" if vector.tail[0] > 0 else "right" - self.draw_y_tick(vector.tail[1], align) - - if scaled_head[0] != 0: - # Show vector head values - offset = 15 if scaled_head[0] > 0 else -15 - total_dashes = int(abs(scaled_head[0] // 20)) - self.__screen.draw_dashed_line( - (0, scaled_head[1]), - (scaled_head[0] - offset, scaled_head[1]), - total_dashes, - dash_size, - self.plotting_config.vector_values_line_width, - self.plotting_config.vector_values_line_color, - self.plotting_config.plotting_speed, - ) # Horizontal dashed line - - offset = 15 if scaled_head[1] > 0 else -15 - total_dashes = int(abs(scaled_head[1] // 20)) - self.__screen.draw_dashed_line( - (scaled_head[0], scaled_head[1] - offset), - (scaled_head[0], 0), - total_dashes, - dash_size, - self.plotting_config.vector_values_line_width, - self.plotting_config.vector_values_line_color, - self.plotting_config.plotting_speed, - ) # Vertical dashed line - - if scaled_head[0] != 0: - align = "up" if vector.head[0] < 0 else "down" - self.draw_x_tick(vector.head[0], align) - align = "left" if vector.head[0] > 0 else "right" - self.draw_y_tick(vector.head[1], align) - def plot_curve(self, curve: _Curve) -> None: """Plots the given two-dimensional curve in the specified interval. diff --git a/curvipy/_screen.py b/curvipy/_screen.py index e3e34b5..768f16b 100644 --- a/curvipy/_screen.py +++ b/curvipy/_screen.py @@ -7,33 +7,66 @@ from typing import Union as _Union _TNumber = _Union[int, float] -_TPoint = tuple[_TNumber, _TNumber] +_TLogicalPoint = tuple[_TNumber, _TNumber] +_TRealPoint = tuple[_TNumber, _TNumber] class ScreenFacade: - """Draw figures such as lines and polylines. - - ScreenFacade encapsulates the turtle package functionalities. + """Screen with a virtual system of coordinates for drawing figures such as lines, polylines, \ + arrows and more. It encapsulates the turtle package functionalities. + + `ScreenFacade` lets the users define a logical (or virtual) screen size by translating the given \ + logical points (a screen position with user virtual coordinates) to a real point (the actual \ + coordinates of the given point). Parameters ---------- window_title : str + Title to display on window. Defaults to "Curvipy". background_color : str + Background color. Can either be a name or a hex color code. Defaults to "#FFFFFF". + window_width : int or None + Width of the screen window (in pixels). If None, `window_width` equals to 50% of the \ + display width. Defaults to None. + window_height : int or None + Height of the screen window (in pixels). If None, `window_height` equals to 75% of \ + the display height. Defaults to None. + logical_width : int + Logical width of the screen. This is the width that the users of `ScreenFacade` class \ + will operate with. + While `window_width` is the real width of the screen, `logical_width` is a virtual \ + representation of it. + logical_height : int + Logical height of the screen. This is the height that the users of `ScreenFacade` class \ + will operate with. + While `window_height` is the real height of the screen, `logical_height` is a virtual \ + representation of it. """ MIN_DRAWING_SPEED = 1 MAX_DRAWING_SPEED = 10 - def __init__(self, window_title: str, background_color: str): + def __init__( + self, + window_title: str, + background_color: str, + window_width: int, + window_height: int, + logical_width: int, + logical_height: int, + ): # Screen setup self.__screen = _turtle.Screen() - self.__screen.screensize(canvwidth=200, canvheight=200) - self.__screen.setup(700, 700) + if window_width and window_height: + self.__screen.setup(window_width, window_height) self.__screen.clear() self.__screen.title(window_title) self.__screen.bgcolor(background_color) self.background_color = background_color + self.logical_width = logical_width + self.logical_height = logical_height + # Pen setup self.__pen = _turtle.Turtle(visible=False) @@ -42,14 +75,68 @@ def __init__(self, window_title: str, background_color: str): self.__pen_color_cache = None def get_screen_size(self) -> tuple[int, int]: - """Returns the width and height of the screen.""" - width, height = self.__screen.screensize() - return width, height + """Returns the real width and height of the screen minus an offset. + + The offset is used to fix `turtle.Screen.window_width()` and \ + `turtle.Screen.window_height()` precision. + """ + width_offset = 65 + height_offset = 65 + width = self.__screen.window_width() + height = self.__screen.window_height() + return width - width_offset, height - height_offset + + def get_real_point(self, logical_point: _TLogicalPoint) -> _TRealPoint: + """Translates the given logical point to a real point. + + Parameters + ---------- + logical_point : tuple[int or float, int or float] + Virtual position to be translated. + + Returns + ------- + tuple[int or float, int or float] + Translated real position. + """ + real_width, real_height = self.get_screen_size() + return ( + logical_point[0] * (real_width / self.logical_width), + logical_point[1] * (real_height / self.logical_height), + ) + + def goto_drawing(self, point: _TRealPoint, drawing_speed: int) -> None: + """Moves pen to the given real point leaving a trace. + + Parameters + ---------- + point : tuple[int or float, int or float] + Point (real position) to which the pen will go leaving a trace. + drawing_speed : int + Speed at which the pen will move. Integer from 1 to 10. + """ + self.__pen.down() + self.__pen.speed(drawing_speed) + self.__pen.goto(point) + + def goto_without_drawing(self, point: _TRealPoint, drawing_speed: int) -> None: + """Moves pen to the given real point without leaving a trace. + + Parameters + ---------- + point : tuple[int or float, int or float] + Point (real position) to which the pen will go. + drawing_speed : int + Speed at which the pen will move. Integer from 1 to 10. + """ + self.__pen.up() + self.__pen.speed(drawing_speed) + self.__pen.goto(point) def draw_line( self, - start_point: _TPoint, - end_point: _TPoint, + start_point: _TLogicalPoint, + end_point: _TLogicalPoint, line_width: int, line_color: str, drawing_speed: int, @@ -59,9 +146,11 @@ def draw_line( Parameters ---------- start_point : tuple[int or float, int or float] - Position at which the line starts. + Logical position at which the line starts. `start_point` is \ + translated to a real position. end_point : tuple[int or float, int or float] - Position at which the line ends. + Logical position at which the line ends. `end_point` is \ + translated to a real position. line_width : int Line width. line_color : str @@ -79,18 +168,16 @@ def draw_line( self.__pen_color_cache = line_color # Go to first line point without drawing - self.__pen.up() - self.__pen.speed(__class__.MAX_DRAWING_SPEED) - self.__pen.goto(start_point) + rstart_point = self.get_real_point(start_point) + self.goto_without_drawing(rstart_point, __class__.MAX_DRAWING_SPEED) # Draw line - self.__pen.down() - self.__pen.speed(drawing_speed) - self.__pen.goto(end_point) + rend_point = self.get_real_point(end_point) + self.goto_drawing(rend_point, drawing_speed) def draw_dashed_line( self, - start_point: _TPoint, - end_point: _TPoint, + start_point: _TLogicalPoint, + end_point: _TLogicalPoint, total_dashes: int, dash_size: int, line_width: int, @@ -102,9 +189,11 @@ def draw_dashed_line( Parameters ---------- start_point : tuple[int or float, int or float] - Position at which the line starts. + Logical position at which the dashed line starts. `start_point` \ + is translated to a real position. end_point : tuple[int or float, int or float] - Position at which the line ends. + Logical position at which the dashed line ends. `end_point` \ + is translated to a real position. total_dashes: int Positive integer. Number of dashes on the line. dash_size : int @@ -124,26 +213,43 @@ def draw_dashed_line( if line_color != self.__pen_color_cache: self.__pen.color(line_color) self.__pen_color_cache = line_color + print( + start_point, + end_point, + total_dashes, + dash_size, + line_width, + line_color, + drawing_speed, + ) + # Variables + rstart_point = self.get_real_point(start_point) + rend_point = self.get_real_point(end_point) # Calculate slope - if start_point == end_point: + if rstart_point == rend_point: return - elif start_point[0] == end_point[0]: + elif rstart_point[0] == rend_point[0]: slope = "inf" # infinite (vertical line) - elif start_point[1] == end_point[1]: + elif rstart_point[1] == rend_point[1]: slope = 0 else: - slope = (end_point[1] - start_point[1]) / (end_point[0] - start_point[0]) + dx = rend_point[0] - rstart_point[0] + dy = rend_point[1] - rstart_point[1] + slope = dy / dx # Calculate line if slope != "inf": - line = lambda t: (t, slope * t + (start_point[1] - start_point[0] * slope)) - t0 = start_point[0] - dt = (end_point[0] - start_point[0]) / total_dashes + line = lambda t: ( + t, + slope * t + (rstart_point[1] - rstart_point[0] * slope), + ) + t0 = rstart_point[0] + dt = (rend_point[0] - rstart_point[0]) / total_dashes else: - line = lambda t: (start_point[0], t) - t0 = start_point[1] - dt = (end_point[1] - start_point[1]) / total_dashes + line = lambda t: (rstart_point[0], t) + t0 = rstart_point[1] + dt = (rend_point[1] - rstart_point[1]) / total_dashes # Draw dashed line for i in range(total_dashes): @@ -176,7 +282,7 @@ def draw_dashed_line( def draw_polyline( self, - points: list[_TPoint], + points: list[_TLogicalPoint], polyline_width: int, polyline_color: str, drawing_speed: int, @@ -186,7 +292,7 @@ def draw_polyline( Parameters ---------- points : list[tuple[int or float, int or float]] - Polyline points positions. + List of the logical position of the polyline points. polyline_width : int Polyline width. polyline_color : str @@ -203,19 +309,22 @@ def draw_polyline( self.__pen.color(polyline_color) self.__pen_color_cache = polyline_color + # Variables + rpoints = [self.get_real_point(point) for point in points] + # Go to first polyline point without drawing self.__pen.speed(__class__.MAX_DRAWING_SPEED) self.__pen.up() - self.__pen.goto(points[0]) + self.__pen.goto(rpoints[0]) # Draw polyline self.__pen.speed(drawing_speed) self.__pen.down() - for point in points[1:]: - self.__pen.goto(point) + for rpoint in rpoints[1:]: + self.__pen.goto(rpoint) def draw_arrow( self, - point: _TPoint, + point: _TLogicalPoint, arrow_size: int, arrow_angle: _TNumber, arrow_width: int, @@ -227,7 +336,7 @@ def draw_arrow( Parameters ---------- point : tuple[int or float, int or float] - Point where the arrow will be drawn. + Logical position where the arrow will be drawn. arrow_size : int Size of the arrow. arrow_angle : str @@ -239,44 +348,43 @@ def draw_arrow( drawing_speed : int Drawing speed. Integer from 1 to 10. """ + # Pen setup + self.__pen.width(arrow_width) + self.__pen.color(arrow_color) + + # Variables setup + rpoint = self.get_real_point(point) + # Draw vector head left_arrow = ( arrow_size * _cos(arrow_angle + _pi * 5 / 4), arrow_size * _sin(arrow_angle + _pi * 5 / 4), ) left_arrow_endpoint = ( - point[0] + left_arrow[0], - point[1] + left_arrow[1], - ) - self.draw_line( - point, - left_arrow_endpoint, - arrow_width, - arrow_color, - drawing_speed, + rpoint[0] + left_arrow[0], + rpoint[1] + left_arrow[1], ) + self.goto_without_drawing(rpoint, __class__.MAX_DRAWING_SPEED) + self.goto_drawing(left_arrow_endpoint, drawing_speed) + right_arrow = ( arrow_size * _cos(arrow_angle - _pi * 5 / 4), arrow_size * _sin(arrow_angle - _pi * 5 / 4), ) right_arrow_endpoint = ( - point[0] + right_arrow[0], - point[1] + right_arrow[1], + rpoint[0] + right_arrow[0], + rpoint[1] + right_arrow[1], ) - self.draw_line( - point, - right_arrow_endpoint, - arrow_width, - arrow_color, - drawing_speed, - ) + # Draw arrow left size + self.goto_without_drawing(rpoint, __class__.MAX_DRAWING_SPEED) + self.goto_drawing(right_arrow_endpoint, drawing_speed) def draw_text( self, text: str, - point: _TPoint, + point: _TLogicalPoint, text_font: tuple[str, str, str], text_color: str, align: str, @@ -288,7 +396,7 @@ def draw_text( text : str Text to be displayed. point : tuple[int or float, int or float] - Point where the text will be drawn. + Logical position where the text will be drawn. text_font : tuple[str, str, str] A triple (fontname, fontsize, fonttype). text_color : str @@ -302,9 +410,10 @@ def draw_text( self.__pen_color_cache = text_color # Draw text + rpoint = self.get_real_point(point) self.__pen.speed(__class__.MAX_DRAWING_SPEED) self.__pen.up() - self.__pen.goto(point) + self.__pen.goto(rpoint) self.__pen.write(text, move=False, align=align, font=text_font) def clean(self) -> None: From c74b5fcce28464a5bd97c9a29b8f9586b556b46d Mon Sep 17 00:00:00 2001 From: Dylan Tintenfich Date: Sat, 31 Dec 2022 19:03:17 -0300 Subject: [PATCH 2/4] Update plotter configuration Changes on 'AxesConfiguration' - Added 'show_axes_direction' - Added '*_ticks_distance' - Removed '*_axis_scale' - Renamed attributes Changes on 'ScreenConfiguration': - Removed 'logical_width' - Removed 'logical_height' Note: now logical width and height is computed automatically by 'curvipy.Plotter' with 'AxesConfiguration.*_ticks' and 'AxesConfiguration.*_ticks_distance'. --- curvipy/_plotter.py | 340 ++++++++++++++++++----------------- docs/source/documentation.md | 45 +++-- 2 files changed, 200 insertions(+), 185 deletions(-) diff --git a/curvipy/_plotter.py b/curvipy/_plotter.py index decc227..a49eac1 100644 --- a/curvipy/_plotter.py +++ b/curvipy/_plotter.py @@ -21,35 +21,23 @@ class ScreenConfiguration: Background color. Can either be a name or a hex color code. Defaults to "#FFFFFF". window_width : int or None Width of the screen window (in pixels). If None, `window_width` equals to 50% of the \ - display width. Defaults to None. + display width. window_height : int or None Height of the screen window (in pixels). If None, `window_height` equals to 75% of \ - the display height. Defaults to None. - logical_width : int - Logical width of the screen. This is the width that `Plotter` will operate with. - While `window_width` is the real width of the screen, `logical_width` is a virtual \ - representation of it. - logical_height : int - Logical height of the screen. This is the height that `Plotter` will operate with. - While `window_height` is the real height of the screen, `logical_height` is a virtual \ - representation of it. + the display height. """ def __init__( self, window_title: str = "Curvipy", background_color: str = "#FFFFFF", - window_width: _Union[int, None] = None, + window_width: int = None, window_height: int = None, - logical_width: int = 20, - logical_height: int = 20, ): self.window_title = window_title self.background_color = background_color self.window_width = window_width self.window_height = window_height - self.logical_width = logical_width - self.logical_height = logical_height class PlottingConfiguration: @@ -113,64 +101,76 @@ class AxesConfiguration: Parameters ---------- show_axes : bool - If true, x-axis and y-axis are shown. Defaults to True. + If True, x-axis and y-axis are shown. Defaults to True. + show_axes_direction : bool + If True, `Plotter` replaces the last tick of both axes with an arrow that \ + indicates the axis direction. Defaults to False. axes_color : str Axis color. Can either be a name or a hex color code. Defaults to "#70CBCE". axes_width : int Axis width. Defaults to 2. - x_axis_scale : int or float - Real value to define x-axis scale. Defaults to 1. - y_axis_scale : int or float - Real value to define y-axis scale. Defaults to 1. - x_axis_ticks : int - Positive integer. Defaults to 10. - y_axis_ticks : int - Positive integer. Defaults to 10. - x_axis_tick_decimals : int - Positive integer. Defaults to 2. - y_axis_tick_decimals : int - Positive integer. Defaults to 2. - tick_number_font : tuple[str, str, str] + ticks_font : tuple[str, str, str] A triple (fontname, fontsize, fonttype). Defaults to ("Verdana", 8, "normal"). - tick_number_color : str + ticks_color : str Ticks text color. Can either be a name or a hex color code. Defaults to "#000000". - x_axis_tick_number_align : str + x_ticks : int + Number of ticks of half the x-axis, i.e. total x-axis ticks equals `2 * x_ticks`. \ + Defaults to 10. + x_ticks_distance : int + Distance between each x-axis tick. E.g. if `x_ticks_distance` equals 1, x-axis \ + ticks will be `{..., -2, -1, 0, 1, 2, ...}`. Defaults to 1. + x_ticks_decimals : int + Number of decimals of the x-axis ticks number. Defaults to 2. + x_ticks_align : str Can either be "top" or "down". Defines if the x-axis ticks number will be placed \ - upside or downside the y-axis. Defaults to "down". - y_axis_tick_number_align : str + upside or downside the x-axis. Defaults to "down". + x_ticks : int + Number of ticks of half the y-axis, i.e. total y-axis ticks equals `2 * y_ticks`. \ + Defaults to 10. + y_ticks_distance : int + Distance between each y-axis tick. E.g. if `y_ticks_distance` equals 0.5, y-axis \ + ticks will be `{..., -1, -0.5, 0, 0.5, 1, ...}`. Defaults to 1. + y_tick_decimals : int + Number of decimals of the y-axis ticks number. Defaults to 2. + y_ticks_align : str Can either be "left" or "right". Defines if the y-axis ticks number will be placed \ - to the left or to the right of the x-axis. Defaults to "left". + to the left or to the right of the y-axis. Defaults to "left". """ def __init__( self, show_axes: bool = True, + show_axes_direction: bool = False, axes_color: str = "#70CBCE", axes_width: int = 2, - x_axis_scale: _TNumber = 1, - y_axis_scale: _TNumber = 1, - x_axis_ticks: int = 10, - y_axis_ticks: int = 10, - x_axis_tick_decimals: int = 2, - y_axis_tick_decimals: int = 2, - tick_number_font: tuple[str, str, str] = ("Verdana", 8, "normal"), - tick_number_color: str = "#000000", - x_axis_tick_number_align: str = "down", - y_axis_tick_number_align: str = "left", + ticks_font: tuple[str, str, str] = ("Verdana", 8, "normal"), + ticks_color: str = "#000000", + x_ticks: int = 10, + x_ticks_distance: int = 1, + x_ticks_decimals: int = 2, + x_ticks_align: str = "down", + y_ticks: int = 10, + y_ticks_distance: int = 1, + y_ticks_decimals: int = 2, + y_ticks_align: str = "left", ): + # General attributes self.show_axes = show_axes + self.show_axes_direction = show_axes_direction self.axes_color = axes_color self.axes_width = axes_width - self.x_axis_scale = x_axis_scale - self.y_axis_scale = y_axis_scale - self.x_axis_ticks = x_axis_ticks - self.y_axis_ticks = y_axis_ticks - self.x_axis_tick_decimals = x_axis_tick_decimals - self.y_axis_tick_decimals = y_axis_tick_decimals - self.tick_number_font = tick_number_font - self.tick_number_color = tick_number_color - self.x_axis_tick_number_align = x_axis_tick_number_align - self.y_axis_tick_number_align = y_axis_tick_number_align + self.ticks_font = ticks_font + self.ticks_color = ticks_color + # X-axis ticks attributes + self.x_ticks = x_ticks + self.x_ticks_distance = x_ticks_distance + self.x_ticks_decimals = x_ticks_decimals + self.x_ticks_align = x_ticks_align + # Y-axis ticks attributes + self.y_ticks = y_ticks + self.y_ticks_distance = y_ticks_distance + self.y_ticks_decimals = y_ticks_decimals + self.y_ticks_align = y_ticks_align class Plotter: @@ -195,6 +195,35 @@ def __init__( plotting_config: PlottingConfiguration = None, axes_config: AxesConfiguration = None, ) -> None: + # Plotter coordinates system: + # + # `curvipy.Plotter` works with a virtual set of coordinates defined by the a logical width + # and height. The logical screen size is independent of the real window size, and virtual + # coordinates are translated to real window coordinates. + # Despite setting a logical width and height do not affect the window size, it defines the + # xy-plane coordinate system. This is useful for precisely calibrating the distance between + # the axes ticks. + # + # Note that the x-axis length equals the logical width and the y-axis length equals the + # logical height. + # + # If we want the x-axis to have `n` total ticks with a distance `Dx` between their self, that is + # + # axis_ticks = {Dx. i | i in N ^ -n/2 <= i <= n/2} + # = {..., -2.Dx, -Dx, 0, Dx, 2.Dx, ....} + # + # and the y-axis to have `m` total ticks with a distance `Dy` between their self, that is + # + # axis_ticks = {Dy. i | i in N ^ -m/2 <= i <= m/2} + # = {..., -2.Dy, -Dy, 0, Dy, 2.Dy, ....} + # + # then + # + # logical_width = n . Dx + # logical_height = m . Dy + # + + # Public attributes if screen_config is not None: self.screen_config = screen_config else: @@ -210,23 +239,29 @@ def __init__( else: self.axes_config = AxesConfiguration() + # Private attributes + total_x_ticks = 2 * self.axes_config.x_ticks + total_y_ticks = 2 * self.axes_config.y_ticks + + self.__logical_width = total_x_ticks * self.axes_config.x_ticks_distance + self.__logical_height = total_y_ticks * self.axes_config.y_ticks_distance + self.__screen = _ScreenFacade( self.screen_config.window_title, self.screen_config.background_color, self.screen_config.window_width, self.screen_config.window_height, - self.screen_config.logical_width, - self.screen_config.logical_height, + self.__logical_width, + self.__logical_height, ) if self.axes_config.show_axes: self._draw_axis() def _draw_axis(self) -> None: - w, h = self.screen_config.logical_width, self.screen_config.logical_height + w, h = self.__logical_width, self.__logical_height - # Y-AXIS - ## Draw y-axis line + # Draw y-axis line self.__screen.draw_line( (0, -h / 2), (0, h / 2), @@ -235,40 +270,31 @@ def _draw_axis(self) -> None: self.__screen.MAX_DRAWING_SPEED, ) - ## Draw y-axis ticks - dy = ( - 0 - if not self.axes_config.y_axis_ticks - else h / (self.axes_config.y_axis_ticks * 2) - ) + # Draw y-axis ticks + dy = 0 if not self.axes_config.y_ticks else h / (self.axes_config.y_ticks * 2) + for i in range(self.axes_config.y_ticks * 2): + y = dy * (i - self.axes_config.y_ticks) + if y != 0: + self.draw_y_tick(y) + + # Draw y-axis last tick or arrow (see `AxesConfiguration.show_axes_direction` attribute description) + if self.axes_config.show_axes_direction: + axis_arrow_pos = (0, h / 2) + axis_arrow_ang = 0.5 * _pi + self.__screen.draw_arrow( + point=axis_arrow_pos, + arrow_angle=axis_arrow_ang, + arrow_size=10, + arrow_width=self.axes_config.axes_width, + arrow_color=self.axes_config.axes_color, + drawing_speed=self.__screen.MAX_DRAWING_SPEED, + ) - if self.axes_config.y_axis_scale > 0: - i_range = range(self.axes_config.y_axis_ticks * 2) else: - i_range = range(self.axes_config.y_axis_ticks * 2, 0, -1) - - for i in i_range: - y = dy * (i - self.axes_config.y_axis_ticks) - if y == 0: - continue - self.draw_y_tick(y / self.axes_config.y_axis_scale) + last_tick = dy * self.axes_config.y_ticks + self.draw_y_tick(last_tick) - ## Draw y-axis line arrow - axis_arrow_pos = ( - (0, h / 2) if self.axes_config.y_axis_scale > 0 else (0, -h / 2) - ) - axis_arrow_ang = 0.5 * _pi if self.axes_config.y_axis_scale > 0 else -0.5 * _pi - self.__screen.draw_arrow( - point=axis_arrow_pos, - arrow_angle=axis_arrow_ang, - arrow_size=10, - arrow_width=self.axes_config.axes_width, - arrow_color=self.axes_config.axes_color, - drawing_speed=self.__screen.MAX_DRAWING_SPEED, - ) - - # X-AXIS - ## Draw x-axis line + # Draw x-axis line self.__screen.draw_line( (-w / 2, 0), (w / 2, 0), @@ -277,35 +303,28 @@ def _draw_axis(self) -> None: self.__screen.MAX_DRAWING_SPEED, ) - ## Draw x-axis ticks - dx = ( - 0 - if not self.axes_config.x_axis_ticks - else w / (self.axes_config.x_axis_ticks * 2) - ) - - if self.axes_config.x_axis_scale > 0: - i_range = range(self.axes_config.x_axis_ticks * 2) + # Draw x-axis ticks + dx = 0 if not self.axes_config.x_ticks else w / (self.axes_config.x_ticks * 2) + for i in range(self.axes_config.x_ticks * 2): + x = dx * (i - self.axes_config.x_ticks) + if x != 0: + self.draw_x_tick(x) + + # Draw x-axis last tick or arrow (see `AxesConfiguration.show_axes_direction` attribute description) + if self.axes_config.show_axes_direction: + axis_arrow_pos = (w / 2, 0) + axis_arrow_ang = 0 + self.__screen.draw_arrow( + point=axis_arrow_pos, + arrow_angle=axis_arrow_ang, + arrow_size=10, + arrow_width=self.axes_config.axes_width, + arrow_color=self.axes_config.axes_color, + drawing_speed=self.__screen.MAX_DRAWING_SPEED, + ) else: - i_range = range(self.axes_config.x_axis_ticks * 2, 0, -1) - - for i in i_range: - x = dx * (i - self.axes_config.x_axis_ticks) - if x == 0: - continue - self.draw_x_tick(x / self.axes_config.x_axis_scale) - - ## Draw x-axis line arrow - point = (w / 2, 0) if self.axes_config.x_axis_scale > 0 else (-w / 2, 0) - axis_arrow_ang = 0 if self.axes_config.x_axis_scale > 0 else _pi - self.__screen.draw_arrow( - point=point, - arrow_angle=axis_arrow_ang, - arrow_size=10, - arrow_width=self.axes_config.axes_width, - arrow_color=self.axes_config.axes_color, - drawing_speed=self.__screen.MAX_DRAWING_SPEED, - ) + last_tick = dx * self.axes_config.x_ticks + self.draw_x_tick(last_tick) def draw_x_tick(self, number: _TNumber, align: str = None) -> None: """Draws a tick on the x-axis. @@ -317,20 +336,20 @@ def draw_x_tick(self, number: _TNumber, align: str = None) -> None: align : str Can either be "top" or "down". Defines if the x-axis tick number will be placed \ upside or downside the y-axis. If `None`, align takes \ - `Plotter.x_axis_tick_number_align` attribute value. Defaults to `None`. + `Plotter.x_ticks_align` attribute value. Defaults to `None`. """ - # Values for `y_offset`, `text_offset` and `tick_length` have been determined + # NOTE: values for `y_offset`, `text_offset` and `tick_length` have been determined # experimentally. This method work properly for small values of text font size # and well spaced ticks. If the given font is too big or ticks are drawn relative # closely, this method might overlap the ticks with their self or with the axes. # Variables setup - w, h = self.screen_config.logical_width, self.screen_config.logical_height - font_size = self.axes_config.tick_number_font[1] - number_str = str(round(number, self.axes_config.x_axis_tick_decimals)) + w, h = self.__logical_width, self.__logical_height + font_size = self.axes_config.ticks_font[1] + number_str = str(round(number, self.axes_config.x_ticks_decimals)) # Offsets - align = self.axes_config.x_axis_tick_number_align if not align else align + align = self.axes_config.x_ticks_align if not align else align if align == "down": y_offset = -0.04 * h # -4% of the y-axis length if align == "up": @@ -340,19 +359,18 @@ def draw_x_tick(self, number: _TNumber, align: str = None) -> None: # Draw x tick tick_length = 0.01 * h # 1% of the y-axis length - x = number * self.axes_config.x_axis_scale self.__screen.draw_line( - (x, -tick_length), - (x, tick_length), + (number, -tick_length), + (number, tick_length), self.axes_config.axes_width, self.axes_config.axes_color, self.__screen.MAX_DRAWING_SPEED, ) self.__screen.draw_text( text=number_str, - point=(x + text_offset, 0 + y_offset), - text_font=self.axes_config.tick_number_font, - text_color=self.axes_config.tick_number_color, + point=(number + text_offset, 0 + y_offset), + text_font=self.axes_config.ticks_font, + text_color=self.axes_config.ticks_color, align="left", ) @@ -366,21 +384,21 @@ def draw_y_tick(self, number: _TNumber, align: str = None) -> None: align : str Can either be "left" or "right". Defines if the y-axis ticks number will be placed \ to the left or to the right of the x-axis. If `None`, align takes \ - `Plotter.y_axis_tick_number_align` attribute value. Defaults to `None`. + `Plotter.y_ticks_align` attribute value. Defaults to `None`. """ - # Values for `y_offset`, `x_offset`, `tick_length` and `tick_length` have been + # NOTE: values for `y_offset`, `x_offset`, `tick_length` and `tick_length` have been # determined experimentally. This method work properly for small values of text # font size and well spaced ticks. If the given font is too big or ticks are drawn # relative closely, this method might overlap the ticks with their self or with the # axes. # Variables setup - w, h = self.screen_config.logical_width, self.screen_config.logical_height - font_size = self.axes_config.tick_number_font[1] - number_str = str(round(number, self.axes_config.y_axis_tick_decimals)) + w, h = self.__logical_width, self.__logical_height + font_size = self.axes_config.ticks_font[1] + number_str = str(round(number, self.axes_config.y_ticks_decimals)) # Offsets - align = self.axes_config.y_axis_tick_number_align if not align else align + align = self.axes_config.y_ticks_align if not align else align if align == "left": text_offset = font_size * len(number_str) * w * 1e-3 x_offset = -0.02 * w - text_offset # 2% of x-axis length plus text offset @@ -391,19 +409,18 @@ def draw_y_tick(self, number: _TNumber, align: str = None) -> None: # Draw y tick tick_length = 0.01 * w # 1% of the x-axis length - y = number * self.axes_config.y_axis_scale self.__screen.draw_line( - (-tick_length, y), - (tick_length, y), + (-tick_length, number), + (tick_length, number), self.axes_config.axes_width, self.axes_config.axes_color, self.__screen.MAX_DRAWING_SPEED, ) self.__screen.draw_text( text=number_str, - point=(0 + x_offset, y + y_offset), - text_font=self.axes_config.tick_number_font, - text_color=self.axes_config.tick_number_color, + point=(0 + x_offset, number + y_offset), + text_font=self.axes_config.ticks_font, + text_color=self.axes_config.ticks_color, align="left", ) @@ -415,33 +432,32 @@ def plot_vector(self, vector: _Vector) -> None: vector : Vector Vector to be plotted. """ + # NOTE: for plotting the vector arrow, we cannot use the vector angle because + # the vector components do not reflect the real coordinates where it will be + # drawn. Because of that, we need the angle of a scaled version of itself, that + # reflect the real coordinates on screen of the vector. + # + # TODO: + # - Display vector components when `PlottingConfiguration.show_vector_values` + # equals True. + # Check if vector is the cero vector (v = [0, 0]) if not vector.norm: return - # Compute scaled vector - scaled_tail = ( - vector.tail[0] * self.axes_config.x_axis_scale, - vector.tail[1] * self.axes_config.y_axis_scale, - ) - scaled_head = ( - vector.head[0] * self.axes_config.x_axis_scale, - vector.head[1] * self.axes_config.y_axis_scale, - ) - scaled_vector = _Vector(scaled_head, scaled_tail) - # Draw vector self.__screen.draw_line( - scaled_tail, - scaled_head, + vector.tail, + vector.head, self.plotting_config.vector_width, self.plotting_config.vector_color, self.plotting_config.plotting_speed, ) # Draw vector head + scaled_vector = _Vector(self.__screen.get_real_point(vector.components)) self.__screen.draw_arrow( - point=scaled_head, + point=vector.head, arrow_angle=scaled_vector.angle, arrow_size=self.plotting_config.vector_head_size, arrow_width=self.plotting_config.vector_width, @@ -457,17 +473,10 @@ def plot_curve(self, curve: _Curve) -> None: curve : Curve Curve to be plotted. """ - # Curve points - scaled_points = [ - ( - self.axes_config.x_axis_scale * point[0], - self.axes_config.y_axis_scale * point[1], - ) - for point in curve.points() - ] # Draw curve + curve_points = curve.points() self.__screen.draw_polyline( - scaled_points, + curve_points, self.plotting_config.curve_width, self.plotting_config.curve_color, self.plotting_config.plotting_speed, @@ -489,7 +498,6 @@ def plot_animated_curve(self, curve: _Curve, samples_per_vector: int) -> None: for i, vector in enumerate(curve.points()): if i % samples_per_vector != 0: continue - self.plot_vector(_Vector(vector)) # Plot curve: diff --git a/docs/source/documentation.md b/docs/source/documentation.md index 3299bc5..6f0c7ef 100644 --- a/docs/source/documentation.md +++ b/docs/source/documentation.md @@ -7,6 +7,13 @@ :members: ``` +## Screen Configuration + +```{eval-rst} +.. autoclass:: curvipy.ScreenConfiguration + :members: +``` + ## Plotting Configuration ```{eval-rst} @@ -25,25 +32,6 @@ With Curvipy you can plot two-dimensional curves. In this section you can find classes provided by Curvipy for defining curves. -## Interval - -```{eval-rst} -.. autoclass:: curvipy.Interval - :members: -``` - -**Example:** -Suppose we want to plot the function √x with 20 samples. - -```python -import curvipy - -valid_interval = curvipy.Interval(start=0, end=10, samples=20) -# All numbers from start to end belong to √x domain. -invalid_interval = curvipy.Interval(start=-10, end=0, samples=20) -# Negative numbers don't belong to √x domain. -``` - ## Curve ```{eval-rst} @@ -66,6 +54,25 @@ class Sin(Curve): You can define your own [Curve](curvipy.Curve) class or use on the classes provided by Curvipy shown below. +## Interval + +```{eval-rst} +.. autoclass:: curvipy.Interval + :members: +``` + +**Example:** +Suppose we want to plot the function √x with 20 samples. + +```python +import curvipy + +valid_interval = curvipy.Interval(start=0, end=10, samples=20) +# All numbers from start to end belong to √x domain. +invalid_interval = curvipy.Interval(start=-10, end=0, samples=20) +# Negative numbers don't belong to √x domain. +``` + ## Function ```{eval-rst} From c380c7923ab3ca8e440e80e4b49d7cc8e6e3cb7f Mon Sep 17 00:00:00 2001 From: Dylan Tintenfich Date: Sat, 31 Dec 2022 20:27:02 -0300 Subject: [PATCH 3/4] Remove display of vector components - Removed 'ScreenFacade.draw_dashed_line()' method - Removed 'PlottingConfiguration.show_vector_values', 'PlottingConfiguration.vector_values_line_color' and 'PlottingConfiguration.vector_values_line_width' attributes. --- curvipy/_plotter.py | 19 +------- curvipy/_screen.py | 107 +------------------------------------------- 2 files changed, 3 insertions(+), 123 deletions(-) diff --git a/curvipy/_plotter.py b/curvipy/_plotter.py index a49eac1..8b78ea0 100644 --- a/curvipy/_plotter.py +++ b/curvipy/_plotter.py @@ -42,7 +42,7 @@ def __init__( class PlottingConfiguration: """Defines the configuration for plotting curves and vectors. - + Parameters ---------- plotting_speed : int @@ -57,14 +57,6 @@ class PlottingConfiguration: Vector width. Defaults to 3. vector_head_size : int Size of vectors' head. Defaults to 10. - show_vector_values : bool - When True, vector head and tail values are shown on the axes. Defaults to True. - vector_values_line_color : str - Color of the dashed line drawn from the vector head and tail to the axes when \ - `show_vector_values` is set to True. Defaults to "#70CBCE". - vector_values_line_width : int - Width of the dashed line drawn from the vector head and tail to the axes. when \ - `show_vector_values` is set to True. Defaults to 3. """ def __init__( @@ -75,9 +67,6 @@ def __init__( vector_color: str = "#E63946", vector_width: int = 3, vector_head_size: int = 10, - show_vector_values: bool = True, - vector_values_line_color: str = "#70CBCE", - vector_values_line_width: int = 3, ): # General attributes self.plotting_speed = plotting_speed @@ -90,9 +79,6 @@ def __init__( self.vector_color = vector_color self.vector_width = vector_width self.vector_head_size = vector_head_size - self.show_vector_values = show_vector_values - self.vector_values_line_color = vector_values_line_color - self.vector_values_line_width = vector_values_line_width class AxesConfiguration: @@ -438,8 +424,7 @@ def plot_vector(self, vector: _Vector) -> None: # reflect the real coordinates on screen of the vector. # # TODO: - # - Display vector components when `PlottingConfiguration.show_vector_values` - # equals True. + # - Display vector components # Check if vector is the cero vector (v = [0, 0]) if not vector.norm: diff --git a/curvipy/_screen.py b/curvipy/_screen.py index 768f16b..356919f 100644 --- a/curvipy/_screen.py +++ b/curvipy/_screen.py @@ -1,5 +1,6 @@ import turtle as _turtle +from math import sqrt as _sqrt from math import sin as _sin from math import cos as _cos from math import pi as _pi @@ -174,112 +175,6 @@ def draw_line( rend_point = self.get_real_point(end_point) self.goto_drawing(rend_point, drawing_speed) - def draw_dashed_line( - self, - start_point: _TLogicalPoint, - end_point: _TLogicalPoint, - total_dashes: int, - dash_size: int, - line_width: int, - line_color: str, - drawing_speed: int, - ) -> None: - """Draws a dashed line from start position to end position. - - Parameters - ---------- - start_point : tuple[int or float, int or float] - Logical position at which the dashed line starts. `start_point` \ - is translated to a real position. - end_point : tuple[int or float, int or float] - Logical position at which the dashed line ends. `end_point` \ - is translated to a real position. - total_dashes: int - Positive integer. Number of dashes on the line. - dash_size : int - Positive integer. - line_width : int - Line width. - line_color : str - Line color. - drawing_speed : int - Drawing speed. Integer from 1 to 10. - """ - # Pen setup - if line_width != self.__pen_width_cache: - self.__pen.width(line_width) - self.__pen_width_cache = line_width - - if line_color != self.__pen_color_cache: - self.__pen.color(line_color) - self.__pen_color_cache = line_color - print( - start_point, - end_point, - total_dashes, - dash_size, - line_width, - line_color, - drawing_speed, - ) - # Variables - rstart_point = self.get_real_point(start_point) - rend_point = self.get_real_point(end_point) - - # Calculate slope - if rstart_point == rend_point: - return - elif rstart_point[0] == rend_point[0]: - slope = "inf" # infinite (vertical line) - elif rstart_point[1] == rend_point[1]: - slope = 0 - else: - dx = rend_point[0] - rstart_point[0] - dy = rend_point[1] - rstart_point[1] - slope = dy / dx - - # Calculate line - if slope != "inf": - line = lambda t: ( - t, - slope * t + (rstart_point[1] - rstart_point[0] * slope), - ) - t0 = rstart_point[0] - dt = (rend_point[0] - rstart_point[0]) / total_dashes - else: - line = lambda t: (rstart_point[0], t) - t0 = rstart_point[1] - dt = (rend_point[1] - rstart_point[1]) / total_dashes - - # Draw dashed line - for i in range(total_dashes): - x, y = line(t0 + dt * i) - if slope != "inf": - dash_start_point = ( - x - dash_size, - y - dash_size * slope, - ) - dash_end_point = ( - x + dash_size, - y + dash_size * slope, - ) - else: - dash_start_point = ( - x, - y - dash_size, - ) - dash_end_point = ( - x, - y + dash_size, - ) - - self.__pen.up() - self.__pen.speed(__class__.MAX_DRAWING_SPEED) - self.__pen.goto(dash_start_point) - self.__pen.down() - self.__pen.speed(drawing_speed) - self.__pen.goto(dash_end_point) - def draw_polyline( self, points: list[_TLogicalPoint], From 2602af8137ed97c85f9523fd95151262cdf9ed00 Mon Sep 17 00:00:00 2001 From: Dylan Tintenfich Date: Sat, 31 Dec 2022 20:33:24 -0300 Subject: [PATCH 4/4] Upgrade version to 1.1.1 --- docs/source/conf.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 634ce69..7723c27 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -22,7 +22,7 @@ project = "curvipy" copyright = "2022, Dylan Tintenfich" author = "Dylan Tintenfich" -release = "1.0.1" +release = "1.1.1" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/setup.py b/setup.py index cfafb34..f144702 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name="curvipy", packages=["curvipy"], - version="1.1.0", + version="1.1.1", license="MIT", description="The Python library for making math animations in a few lines of code.", author="Dylan Tintenfich",