|
#!/usr/local/bin/python |
|
|
|
import gi |
|
gi.require_version('Gtk', '3.0') |
|
from gi.repository import Gtk, Gdk |
|
import math |
|
|
|
|
|
class Sierpinski(Gtk.Window): |
|
def __init__(self): |
|
super(Sierpinski, self).__init__() |
|
self.drag_coords = [] |
|
self.coords = self.get_coords([50, 400], 400) |
|
self.increment_p = 0.10 |
|
self.tresh = 800 |
|
self.depths = 3 |
|
self._add_depths = 1 |
|
self.init_ui() |
|
|
|
@property |
|
def length(self): |
|
"""Outermost triangle's side length""" |
|
return abs(self.coords[2][0] - self.coords[0][0]) |
|
|
|
def init_ui(self): |
|
self.darea = Gtk.DrawingArea() |
|
self.darea.connect("draw", self.on_draw) |
|
self.darea.set_events(Gdk.EventMask.ALL_EVENTS_MASK) |
|
self.add(self.darea) |
|
self.darea.connect("button-press-event", self.on_button_press) |
|
self.darea.connect("button-release-event", self.on_button_release) |
|
self.darea.connect("scroll-event", self.on_scroll) |
|
self.set_title("Sierpinski Triangle Explorer") |
|
self.resize(500, 500) |
|
self.set_position(Gtk.WindowPosition.CENTER) |
|
self.connect("delete-event", Gtk.main_quit) |
|
self.show_all() |
|
|
|
def mid_point(self, a, b): |
|
'''Returns the coordinates of the middle point found on the segment |
|
passing between point a and point b''' |
|
return ((a[0] + b[0]) / 2, (a[1] + b[1]) / 2) |
|
|
|
def get_coords(self, bott_left, length): |
|
"""Returns a list containing three pairs of coordinates representing |
|
the three vertices of an equilateral triangle having bott_left |
|
coordinates as bottom left vertex and side's length as length""" |
|
height = length * math.sqrt(3) / 2 |
|
top = [(length / 2) + bott_left[0], bott_left[1] - height] |
|
bott_right = [bott_left[0] + length, bott_left[1]] |
|
return [bott_left, top, bott_right] |
|
|
|
def move_coords(self): |
|
'''Translates - updating in place - self.coords by the same amount of |
|
space (and maintaining the direction) separating the two points in |
|
self.drag_coords''' |
|
if len(self.drag_coords) == 2: |
|
delta_x = (self.drag_coords[1][0] - self.drag_coords[0][0]) |
|
delta_y = (self.drag_coords[1][1] - self.drag_coords[0][1]) |
|
for coord in self.coords: |
|
coord[0] += delta_x |
|
coord[1] += delta_y |
|
|
|
def zoom_coords(self, p): |
|
'''Increaases (or decreases) the distance beetween the three points in |
|
self.coords maintaining the triangle proportions, by percentage p. |
|
p value must be between -1 and 1 (e.g. -0.1 decreases the distance by 10%)''' |
|
bott_left = self.coords[0] |
|
bott_right = self.coords[2] |
|
increment = p * self.length |
|
new_length = self.length + increment |
|
bott_left[0] -= (increment / 2) |
|
bott_left[1] += (increment / 2) |
|
self.coords = self.get_coords(bott_left, new_length) |
|
|
|
def _draw_triangle(self, coords, cr): |
|
'''Draws three straight lines between respectively 0, 1, 2 points in |
|
coords''' |
|
cr.move_to(coords[0][0], coords[0][1]) |
|
cr.line_to(coords[1][0], coords[1][1]) |
|
cr.line_to(coords[2][0], coords[2][1]) |
|
cr.line_to(coords[0][0], coords[0][1]) |
|
cr.stroke() |
|
|
|
def _draw_fractal(self, coords, depth, cr): |
|
'''Draws Sierpinski fractal, given three points (coords) and a positive |
|
integer number (depths) representing respectively the three vertices of |
|
a triangle and the level of sub-triangles to draw. |
|
|
|
Eg. depth == 0 --> draws a single triangle |
|
depth == 1 --> draws a "Triforce" |
|
depth == 2 --> draws a "Triforce" in the perimetral triangles |
|
... |
|
''' |
|
self._draw_triangle(coords, cr) |
|
if depth > 0: |
|
self._draw_fractal([coords[0], |
|
self.mid_point(coords[0], coords[1]), |
|
self.mid_point(coords[0], coords[2])], |
|
depth - 1, cr) |
|
self._draw_fractal([coords[1], |
|
self.mid_point(coords[0], coords[1]), |
|
self.mid_point(coords[1], coords[2])], |
|
depth - 1, cr) |
|
self._draw_fractal([coords[2], |
|
self.mid_point(coords[2], coords[1]), |
|
self.mid_point(coords[0], coords[2])], |
|
depth - 1, cr) |
|
|
|
def on_draw(self, wid, cr): |
|
cr.set_source_rgb(0, 0, 0) |
|
cr.set_line_width(1) |
|
self._draw_fractal(self.coords, self.depths + self._add_depths, cr) |
|
|
|
def on_button_press(self, w, e): |
|
'''Saves the coordinates of the mouse pointer on the drawing area when |
|
the left button is pressed''' |
|
self.drag_coords.append((e.x, e.y)) |
|
|
|
def on_button_release(self, w, e): |
|
'''Saves the coordinates of the mouse pointer on the drawing area when |
|
the left button is released. Then calls self.move_coords to translate |
|
the figure coordinates and re-draws the figure.''' |
|
self.drag_coords.append((e.x, e.y)) |
|
self.move_coords() |
|
self.drag_coords = [] |
|
self.darea.queue_draw() |
|
|
|
def on_scroll(self, w, e): |
|
'''Zooms in and out when the mouse wheel (scroll) is rotated''' |
|
if e.delta_y == -1: |
|
self.zoom_coords(self.increment_p) |
|
if int(self.length / self.tresh) > 0: |
|
self._add_depths += 1 |
|
self.tresh = self.tresh * 2 |
|
self.darea.queue_draw() |
|
elif e.delta_y == 1 and self.length > 16: |
|
self.zoom_coords(-self.increment_p) |
|
if int(self.length / (self.tresh / 2)) == 0: |
|
if self._add_depths > 1: |
|
self._add_depths -= 1 |
|
self.tresh = self.tresh / 2 |
|
self.darea.queue_draw() |
|
|
|
|
|
def main(): |
|
app = Sierpinski() |
|
Gtk.main() |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |