From 86c84e29dc8db2eef474d1fc692487481a741d3e Mon Sep 17 00:00:00 2001 From: Dylan Tintenfich Date: Fri, 30 Dec 2022 00:15:36 -0300 Subject: [PATCH] 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: