Skip to content

Lines & Curved Lines!

Bhavye Mathur edited this page Jul 13, 2020 · 11 revisions

The Line Class

The line class helps you draw straight line segments very simply just like this:

from goopylib.objects.Line import Line  # To only import the Line object
from goopylib.objects.imports import *  # Import all the basic objects & classes required to make an application/game

window = GraphWin("Creating a line!", width=800, height=800, autoflush=False)

line_example = Line(Point(200, 200), Point(500, 300)).draw(window)  # This should create a simple straight line between the 2 coordinates

while True:
    window.update_win()

Example Screenshot

Lines can also be given more than 2 coordinates like this:

l = Line(Point(100, 400), Point(300, 200), Point(700, 400)).draw(window)

Example Screenshot 2

Line Options

There are a few arguments which you can customize in the Line object and here they are listed with what type the argument expects in brackets.

  1. outline - The colour of the line, see Colours in Goopy! (Colour)
  2. outline_width - The width of the line in pixels (int)
  3. arrow - The line can have an arrow on the front, back, or both sides. This parameter must be one of None (or 'none'), 'both', 'first', 'last' (string or None)

Arrow Shape

Just as an example, here is the code used to create the image above:

from goopylib.imports import *
window = GraphWin("Test Window", width=800, height=800, autoflush=False)

Line(Point(100, 100), Point(400, 100), outline=BLACK).draw(window)
Line(Point(100, 150), Point(400, 150), outline=BLACK, arrow="first").draw(window)
Line(Point(100, 200), Point(400, 200), outline=BLACK, arrow="last").draw(window)
Line(Point(100, 250), Point(400, 250), outline=BLACK, arrow="both").draw(window)

Text(Point(250, 115), "No Arrow - 'none'", font_size=10).draw(window)
Text(Point(250, 165), "No Arrow - 'first'", font_size=10).draw(window)
Text(Point(250, 215), "No Arrow - 'last'", font_size=10).draw(window)
Text(Point(250, 265), "No Arrow - 'both'", font_size=10).draw(window)

while True:
    window.update_win()
  1. arrow_shape - This defines the shape of the arrow (defaults to (8, 10, 3)) in the form (d1, d2, d3) (tuple or list)

Arrow Shape

from anzeljg.github.io/rin2/book2/2405/docs/tkinter/create_line

  1. arrow_scale - This is 0.5 by default and controls how the arrow on the line scales as the width of the line increases. If it is 0.5, the shape of the arrow will be scaled by 0.5 * outline_width. If this is 0, the arrow won't scale with the width. (int or float)
  2. capstyle - This defines how the line looks at the start & end. This must be one of round, butt, or projecting. (string)
  3. joinstyle - This defines how the line joins with other segments. This must be one of round, bevel, or miter. (string)

Line Styles

  1. cursor - What cursor should be shown when the mouse hovers over the Line? see Cursors in Goopy
  2. dash - You can change this to change whether or not the line is dotted or solid. The values can be one of solid, dash, long dash, dot, dashdot, or long dashdot or it can be a tuple as detailed here anzeljg.github.io/rin2/book2/2405/docs/tkinter/dash-patterns.

dash=(5, 1, 2, 1) and dashoff=3, the first pattern produced will be: 2 on, 1 off, 2 on, and 1 off. Subsequent patterns will be 5 on, 1 off, 2 on, and 1 off.

  1. style - This is a string referencing a config style (string)
  2. bounds_width - This dictates the thickness of the bounding box of the line, see the paragraph on clickable lines for more.

All Line Functions

Important Functions

  1. is_clicked(mouse_pos) - Checks if the line has been clicked given a Point() object.

Clickable Lines: Lines are a very unique object because of their ability to twist and turn and the fact that they might only be a few pixels thick. The latter may become a large problem if you are working with the is_clicked() function as the user might get frustrated due to the seeming inability to click a line just a few pixels wide.

For this reason, the bounds_width argument is very important. By setting this argument to a higher number (say about 20 or so), the user gets more room to click the Line. That is, this argument widens the bounding box without effecting how the line looks.

Getter Functions

  1. get_anchor() - Returns the center of the Line
  2. get_size() - Returns the size (width, height) of the line
  3. get_width()
  4. get_height()
  5. get_outline_width()
  6. get_fill()
  7. get_arrow()
  8. get_outline()
  9. get_cursor()
  10. get_capstyle()
  11. get_joinstyle()
  12. get_arrow_shape()
  13. get_bounds_width()

Setter Functions

The inputs to these functions is the same as that as the __init__ function.

  1. set_arrow(option)
  2. remove_arrows()
  3. set_arrow_both()
  4. set_arrow_first()
  5. set_arrow_last()
  6. set_outline(outline)
  7. set_outline_width(outline_width) 8 set_capstyle(style)
  8. set_joinstyle(style)
  9. set_arrow_shape(shape, scale=True) - If scale is True, the arrow of the line scales with its width, if False, its size stays constant.
  10. set_dash(dash)
  11. set_bounds_width(width)

Curved Line Class

The curved line class is almost identical to the Line class - in fact, it is a subclass of the Line class. The only major difference with the curved line object is well... its ability to be curved. Here is how you would create a curved line:

from goopylib.objects.CurvedLine import CurvedLine  # To only import the Curved Line object
from goopylib.objects.imports import *  # Import all the basic objects & classes required to make an application/game

window = GraphWin("Creating a curved line!", width=800, height=800, autoflush=False)

line = CurvedLine(Point(100, 400), Point(400, 100), Point(700, 400), resolution=10, interpolation="cubic").draw(window)

while True:
    window.update_win()

You will notice we provided it an argument called resolution, & interpolation. The resolution argument dictates how 'close' the curved line is to the interpolation provided. Essentially, a curved line is created by taking the coordinates of the normal line, and interpolating between them resolution times. The algorithm used to interpolate is defined by the interpolation parameter which can be any one of linear, cosine (default), cubic, hermit, or spline.

As the resolution increases, the more accurate the line gets to the actual curved line and the better bounding-box it has. I find a resolution of at least 3 is required and nothing more than 10 may be needed.

Interpolation Comparisions

You can see here a comparison between the different interpolation techniques.

  • 'linear' - Red
  • 'cosine' - Blue
  • 'cubic' - Violet
  • 'spline' - Green

And here is the code used for this example:

from goopylib.imports import *

window = GraphWin("Test Window", width=800, height=800, autoflush=False)

CurvedLine(Point(100, 400), Point(400, 100), Point(700, 400), resolution=10, outline=RED, interpolation="linear").draw(window)
CurvedLine(Point(100, 400), Point(400, 100), Point(700, 400), resolution=10, outline=BLUE, interpolation="cosine").draw(window)
CurvedLine(Point(100, 400), Point(400, 100), Point(700, 400), resolution=10, outline=VIOLET, interpolation="cubic").draw(window)
CurvedLine(Point(100, 400), Point(400, 100), Point(700, 400), resolution=10, outline=GREEN, interpolation="spline").draw(window)

while True:
    window.update_win()

Here is a comparison of using different resolutions with the lines. One thing to note is that even though these lines may looks very similar (resolution > 3 especially), they might not function properly with the is_clicked() function and you may not be able to detect certain clicks. For this purpose, a resolution above 5 is recommended (10 if possible).

Resolution Comparisions

The code used for the above is:

from goopylib.imports import *

window = GraphWin("Test Window", width=800, height=800, autoflush=False)

x = 10

colour_grad1 = ColourGradient(LIGHTER_GREEN, DARKEST_GREEN, divisions=x)
colour_grad2 = ColourGradient(LIGHTER_BLUE, DARKEST_BLUE, divisions=x)
colour_grad3 = ColourGradient(LIGHTER_PURPLE, DARKEST_PURPLE, divisions=x)

points = [Point(50, 400), Point(150, 200), Point(200, 600), Point(400, 100), Point(500, 200), Point(700, 700)]

for p in points:  # Drawing the Points
    Circle(p, 5, fill=VIOLET, outline_width=0).draw(window)

for i in range(x):
    CurvedLine(*points, interpolation="spline", resolution=i, outline=colour_grad1[i]).draw(window)
    CurvedLine(*points, interpolation="cosine", resolution=i, outline=colour_grad2[i]).draw(window)
    CurvedLine(*points, interpolation="cubic", resolution=i, outline=colour_grad3[i]).draw(window)

while True:
    window.update_win()

Now you may notice that my graph doesn't contain a line with the Hermite interpolation and this is because this is slightly different. If you use the Hermite interpolation, you CAN (not required) supply 2 other variables: bias (default 0) and tension (default 1) which also dictate how the curve looks.

Clone this wiki locally