Grafikk 2


Tegne sentrerte figurer
from uib_inf100_graphics import *

def redraw_all(app, canvas):
    # Tilnærming #1: Legg til margin venstre/topp, trekk fra margin høyre/bunn:
    # Gir enkelt kontroll på avstand til kanten
    margin = 50
    width = app.width
    height = app.height
    canvas.create_rectangle(margin, margin, width-margin, height-margin,
                            fill='darkred', outline='black')

    # Tilnærming #2: Regn ut sentrum (cx, cy). Så, legg til/trekk fra halve
    # bredden for venstre/høyre, og legg til/trekk fra halve høyden for
    # topp/bunn. Gir enkel kontroll på størrelsen til elementet som tegnes.
    (cx, cy) = (app.width/2, app.height/2)
    (rectWidth, rectHeight) = 60, 30
    canvas.create_rectangle(cx - rectWidth/2, cy - rectHeight/2,
                            cx + rectWidth/2, cy + rectHeight/2,
                            fill='gold', outline='black')

run_app(width=400, height=200)

Illustrasjon av koden over

Hjelpefunksjoner for grafikk
from uib_inf100_graphics import *

def draw_belgian_flag(canvas, x0, y0, x1, y1):
    """ Tegner et Belgisk flagg i området som er avgrenset av (x0, y0)
    i hjørnet til venstre oppe, og av hjørnet (x1, y1) i hjørnet 
    til høyre nede."""
    width = (x1 - x0)
    canvas.create_rectangle(x0, y0, x0+width/3, y1, fill='black', width=0)
    canvas.create_rectangle(x0+width/3, y0, x0+width*2/3, y1,
                            fill='yellow', width=0)
    canvas.create_rectangle(x0+width*2/3, y0, x1, y1, fill='red', width=0)

def redraw_all(app, canvas):
    # Tegn et stort flagg
    draw_belgian_flag(canvas, 25, 25, 175, 150)

    # Og et mindre flagg under
    draw_belgian_flag(canvas, 75, 160, 125, 200)

    # La oss tegne et rutenett med flagg
    flag_width = 30
    flag_height = 25
    margin = 5
    for row in range(4):
        for col in range(6):
            left = 200 + col * flag_width + margin
            top = 50 + row * flag_height + margin
            right = left + flag_width - margin
            bottom = top + flag_height - margin
            draw_belgian_flag(canvas, left, top, right, bottom)

run_app(width=400, height=200)

Illustrasjon av koden over

La oss tegne det japanske flagget. Her må vi sentrere en rød (med rgb-verdi #BC002C) sirkel i et hvitt flagg. Se kravene til flagget under

Krav til størrelsesforhold for det japanske flaggetFigur CC-BY-SA-3.0 av Zscout370 via Wikimedia Commons Wikipedia.

from uib_inf100_graphics import *

def draw_japanese_flag(canvas, x0, y0, x1, y1):
    """ Tegner et Japansk flagg i området som er avgrenset av (x0, y0)
    i hjørnet til venstre oppe, og av hjørnet (x1, y1) i hjørnet 
    til høyre nede."""

    height = (y1 - y0)
    r = (height * 3 / 5) / 2

    cx = (x0 + x1) / 2
    cy = (y0 + y1) / 2
    canvas.create_rectangle(x0, y0, x1, y1, outline="black")
    canvas.create_oval(cx-r, cy-r, cx+r, cy+r, fill="#BC002C", width=0)

def redraw_all(app, canvas):
    # Tegn et stort flagg
    draw_japanese_flag(canvas, 25, 25, 25 + 150, 25 + 100)

    # Og et mindre flagg under
    draw_japanese_flag(canvas, 75, 150, 75 + 60, 150 + 40)

    # La oss tegne et rutenett med flagg
    margin = 5
    flag_width = 27 + margin
    flag_height = 18 + margin
    for row in range(4):
        for col in range(6):
            left = 200 + col * flag_width + margin
            top = 50 + row * flag_height + margin
            right = left + flag_width - margin
            bottom = top + flag_height - margin
            draw_japanese_flag(canvas, left, top, right, bottom)

run_app(width=400, height=200)

Illustrasjon av koden over

Dynamisk tekststørrelse
from uib_inf100_graphics import *

def redraw_all(app, canvas):
    # Dynamisk størrelse for tekst er ikke like rett frem som
    # geometriske figurer med koodinater basert på vinduets høyde 
    # og bredde, men det er fremdeles mulig.

    # Regn font-størrelse basert på f. eks. vinduets bredde
    # Litt prøving og feiling for å finne en god formel.
    textSize = app.width // 15
    text = 'Endre vindustørrelse!'
    canvas.create_text(app.width/2, app.height/2, text=text,
                        font=f'Arial {textSize} bold', fill='black')

run_app(width=400, height=200)

Illustrasjon av koden over

Tigonometri og sirkulære mønster

Dette avsnitt om trigonometri er ikke pensum i INF100, men kan være nyttig om du ønsker å tegne figurer elle mønstre som «går i ring,» slik som stjerner, klokker, n-kanter og lignende.

De aller fleste rammeverk for grafikk, inkludert tkinter og uib_inf100_graphics, benytter et koordinatsystem hvor y vokser nedover. Når vi jobber med trigonometri og vinkler må vi derfor huske å snu opp ned også her. Legg merke til at gradvis økende vinkel \(\theta\) vil snurre med klokken, og ikke mot klokken slik vi er vant til fra grunnskolematematikken.

Trigonometri når y vokser nedover

from uib_inf100_graphics import *

import math

def draw_clock_face(canvas, cx, cy, r):
    canvas.create_oval(cx-r, cy-r, cx+r, cy+r, fill='yellow', outline='black')
    r *= 0.85 # mindre radius for å tegne tallene på klokken
    for hour in range(12):
        # Rett opp tilsvarer en vinkel på -pi/2 radianer
        # (husk, y-aksen vokser nedover)
        hour_theta = -math.pi/2 + (2*math.pi)*(hour/12)
        hour_x = cx + r * math.cos(hour_theta)
        hour_y = cy + r * math.sin(hour_theta)
        label = str(hour if hour > 0 else 12)
        canvas.create_text(hour_x, hour_y, text=label,
                           font='Arial 14 bold', fill='black')

def redraw_all(app, canvas):
    draw_clock_face(canvas, cx=app.width/2, cy=app.height/2,
                    r=min(app.width, app.height)/3)

run_app(width=300, height=200)

Illustrasjon av koden over

Klokker

Dette avsnittet om klokker er ikke pensum i INF100, men kan være et fint eksempel å referere til om man ønsker å tegne figurer med sirkulære mønstre.

from uib_inf100_graphics import *

import math

def draw_clock_face(canvas, cx, cy, r):
    canvas.create_oval(cx-r, cy-r, cx+r, cy+r, fill='yellow', outline='black')

def draw_clock_hands(canvas, cx, cy, r, hour, minute):
    # Juster timene til 12-tallsystemet
    hour %= 12

    # Juster timene slik at de tar hensyn til minuttene også
    hour += minute / 60

    # Time-viseren
    hour_r = r * 0.5
    hour_theta = -math.pi/2 + (2*math.pi)*(hour/12)
    hour_x = cx + hour_r * math.cos(hour_theta)
    hour_y = cy + hour_r * math.sin(hour_theta)
    canvas.create_line(cx, cy, hour_x, hour_y, fill="black")

    # Minutt-viseren
    minute_r = r * 0.9
    minute_theta = -math.pi/2 + (2*math.pi)*(minute/60)
    minute_x = cx + minute_r * math.cos(minute_theta)
    minute_y = cy + minute_r * math.sin(minute_theta)
    canvas.create_line(cx, cy, minute_x, minute_y, fill="black")

def draw_clock(canvas, x0, y0, x1, y1, hour, minute):
    canvas.create_rectangle(x0, y0, x1, y1, outline="black")
    (cx, cy) = (x0 + x1) / 2, (y0 + y1) / 2
    width = abs(x0 - x1)
    height = abs(y0 - y1)
    r = min(width, height) / 2

    draw_clock_face(canvas, cx, cy, r)
    draw_clock_hands(canvas, cx, cy, r, hour, minute)


def redraw_all(app, canvas):
    # En stork klokke som viser tiden 14:30
    draw_clock(canvas, 25, 25, 175, 150, 14, 30)

    # En mindre klokke under som viser tiden 07:45
    draw_clock(canvas, 75, 160, 125, 200, 7, 45)

    # Et helt rutenett med klokker
    width = 40
    height = 40
    margin = 5
    hour = 0
    for row in range(3):
        for col in range(4):
            left = 200 + col * width + margin
            top = 50 + row * height + margin
            right = left + width - margin
            bottom = top + height - margin
            hour += 1
            draw_clock(canvas, left, top, right, bottom, hour, 0)

run_app(width=400, height=230)

Illustrasjon av koden over

La klokken komme til live med hjelp av app_started og timer_fired. Dette er konsepter som kommer om en uke eller to, men en liten forsmak skader ikke :)

from uib_inf100_graphics import *

import math

def app_started(app):
    # app_started kalles én gang når programmet starter
    # vi kan lage variabler som hører hjemme inne i "app"
    app.hour = 0
    app.minute = 0

def timer_fired(app):
    # Denne metoden kalles periodisk med et gitt intervall (standard: 100ms)
    # Vi skrur tiden frem med ett minutt
    app.minute += 1
    if app.minute >= 60:
        app.minute = 0
        app.hour = (app.hour + 1) % 24


def draw_clock_face(canvas, cx, cy, r):
    canvas.create_oval(cx-r, cy-r, cx+r, cy+r, fill='yellow', outline='black')
    r *= 0.85 # mindre radius for å tegne tallene på klokken
    for hour in range(12):
        # Rett opp tilsvarer en vinkel på -pi/2 radianer (husk, y-aksen vokser nedover)
        hour_theta = -math.pi/2 + (2*math.pi)*(hour/12)
        hour_x = cx + r * math.cos(hour_theta)
        hour_y = cy + r * math.sin(hour_theta)
        label = str(hour if hour > 0 else 12)
        canvas.create_text(hour_x, hour_y, text=label,
                           font='Arial 14 bold', fill='black')

def draw_clock_hands(canvas, cx, cy, r, hour, minute):
    # Juster timene til 12-tallsystemet
    hour %= 12

    # Juster timene slik at de tar hensyn til minuttene også
    hour += minute / 60

    # Time-viseren
    hour_r = r * 0.5
    hour_theta = -math.pi/2 + (2*math.pi)*(hour/12)
    hour_x = cx + hour_r * math.cos(hour_theta)
    hour_y = cy + hour_r * math.sin(hour_theta)
    canvas.create_line(cx, cy, hour_x, hour_y, fill="black")

    # Minutt-viseren
    minute_r = r * 0.9
    minute_theta = -math.pi/2 + (2*math.pi)*(minute/60)
    minute_x = cx + minute_r * math.cos(minute_theta)
    minute_y = cy + minute_r * math.sin(minute_theta)
    canvas.create_line(cx, cy, minute_x, minute_y, fill="black")

def draw_clock(canvas, x0, y0, x1, y1, hour, minute):
    # Tegn en rektangel rundt området hvor klokken skal være
    canvas.create_rectangle(x0, y0, x1, y1, outline="black")

    # Regn ut sentrum og radius av klokken
    (cx, cy) = (x0 + x1) / 2, (y0 + y1) / 2
    width = abs(x0 - x1)
    height = abs(y0 - y1)
    r = min(width, height) / 2

    # Tegn først klokken, så tegnes viserne oppå
    draw_clock_face(canvas, cx, cy, r)
    draw_clock_hands(canvas, cx, cy, r, hour, minute)


def redraw_all(app, canvas):
    draw_clock(canvas, app.width/5, app.height/5, app.width*4/5, app.height*4/5, app.hour, app.minute)

run_app(width=300, height=200)

Illustrasjon av koden over