Прежде чем продемонстрировать вам PyClock, немного предыстории и признаний. Ну-ка, ответьте: как поставить точки на окружности? Эта задача, а также форматы времени и возникающие события оказываются основными при создании графических элементов часов. Чтобы нарисовать циферблат аналоговых часов на холсте, необходимо уметь рисовать круг — сам циферблат состоит из точек окружности, а секундная, минутная и часовая стрелки представляют собой линии, проведенные из центра в точки на окружности. Цифровые часы нарисовать проще, но смотреть на них неинтересно.
Теперь признание: начав писать PyClock, я не знал ответа на первый вопрос предыдущего абзаца. Я совершенно забыл формулу нахождения координат точек окружности (как и большинство профессиональных программистов, к которым я с этим обращался). Бывает. Такие знания, не будучи востребованными в течение нескольких десятилетий, могут быть утилизированы сборщиком мусора. В конце концов мне удалось смахнуть пыль с нескольких нейронов, длины которых оказалось достаточно, чтобы запрограммировать действия, необходимые для построения, но блеснуть умом мне не удалось.1
Если вы в таком же положении, то я покажу вам один способ простой записи формул построения точек на языке Python, хотя для подробных занятий геометрией места здесь нет. Прежде чем взяться за более сложную задачу реализации часов, я написал сценарий plotterGui, представленный в примере 11.11, чтобы сосредоточиться только на логике построения круга.
Логика построения круга реализуется в функции point — она находит координаты (X,Y) точки окружности по относительному номеру точки, общему количеству точек, помещаемых на окружности, и радиусу окружности (расстоянию между центром окружности и ее точками). Сначала вычисляется угол между нужной точкой и верхней точкой окружности путем деления 360 на количество рисуемых точек и умножения на номер точки. Напомню, что полный круг составляет 360 градусов (например, если на окружности рисуется 4 точки, то каждая отстоит от предыдущей на 90 градусов, или на 360/4). Стандартный модуль Python math предоставляет все необходимые константы и функции — pi, sine и cosine. В действительности математика тут не такая уж непонятная, если вы потратите некоторое время, чтобы ее рассмотреть (возможно, еще взяв старый учебник геометрии). Существуют альтернативные способы реализации нужных математических расчетов, но я не буду углубляться здесь в детали (ищите подсказки в пакете с примерами).
Даже если вы не хотите разбираться с математикой, просмотрите функцию circle в примере 11.11. По указанным координатам (X,Y) точ-
Чтобы не выставлять программистов в невыгодном свете, следует отметить, что ко мне неоднократно обращались с просьбами прочитать лекции о программировании на языке Python для физиков, которые имели более богатую математическую практику, чем я, но многие из которых благополучно злоупотребляли общими блоками и операторами GO TO языка FORTRAN. Специализация в профессиональной деятельности может всех нас в чем-то превратить в новичков.
ки окружности, возвращаемым функцией point, она чертит линию из центра окружности в точку и маленький прямоугольник вокруг самой точки, что несколько напоминает стрелки и отметки аналоговых часов. Чтобы удалять нарисованные объекты перед каждым построением, используются теги холста.
Пример 11.11. PP4E\Gui\Clock\plotterGui.py
# рисует окружности на холсте
import math, sys
from tkinter import *
def point(tick, range, radius):
angle = tick * (360.0 / range)
radiansPerDegree = math.pi / 180
pointX = int( round( radius * math.sin(angle * radiansPerDegree) )) pointY = int( round( radius * math.cos(angle * radiansPerDegree) )) return (pointX, pointY)
def circle(points, radius, centerX, centerY, slow=0):
canvas.delete(‘lines’)
canvas.delete(‘points’)
for i in range(points):
x, y = point(i+1, points, radius-4)
scaledX, scaledY = (x + centerX), (centerY — y) canvas.create_line(centerX, centerY, scaledX, scaledY, tag=’lines’) canvas.create_rectangle(scaledX-2, scaledY-2, scaledX+2, scaledY+2, fill=’red’, tag=’points’)
if slow: canvas.update()
def plotter(): # в 3.x // — деление с усечением
circle(scaleVar.get(), (Width // 2), originX, originY, checkVar.get())
def makewidgets():
global canvas, scaleVar, checkVar
canvas = Canvas(width=Width, height=Width)
canvas.pack(side=TOP)
scaleVar = IntVar()
checkVar = IntVar()
scale = Scale(label=’Points on circle’, variable=scaleVar, from_=1,
to=360)
scale.pack(side=LEFT)
Checkbutton(text=’Slow mode’, variable=checkVar).pack(side=LEFT) Button(text=’Plot’, command=plotter).pack(side=LEFT, padx=50)
if __name__ == ‘__main__’:
Width = 500 # ширина, высота по умолчанию
if len(sys.argv) == 2: Width = int(sys.argv[1]) # ширина в команд. строке?
originX = originY = Width // 2 # то же, что и радиус
makewidgets() # в корневом окне Tk по умолчанию
mainloop() # в 3.x требуется // — деление с усечением
По умолчанию ширина круга составит 500 пикселей, если не определить иначе в командной строке. Получив число точек на окружности, этот сценарий размечает окружность по часовой стрелке при каждом нажатии кнопки Plot, вычерчивая прямые из центра к маленьким прямоугольникам на окружности. Переместите ползунок, чтобы задать другое число точек, и щелкните на флажке, чтобы рисование происходило достаточно медленно и можно было заметить очередность вычерчивания линий и точек (при этом сценарий вызывает update для обновления экрана после вычерчивания каждой линии). На рис. 11.19 приводится результат нанесения 120 точек при установке в командной строке ширины круга равной 400; если задать на окружности 60 или 12 точек, сходство с часовым циферблатом станет более заметным.
Рис. 11.19. Сценарий plotterGui в действии
Дополнительную помощь могут оказать ориентированные на текстовый, а не на графический вывод версии этого сценария, имеющиеся в дереве примеров, которые выводят координаты точек окружности в поток stdout, а не отображают эти точки в графическом интерфейсе. Смотрите сценарии plotterText.py в каталоге часов. Ниже показано, что он выводит для случаев 4 и 12 точек на окружности шириной 400 точек. Формат вывода прост:
номер_точки: угол = (координатах, KoopguHamaY)
Предполагается, что центр круга имеет координаты (0,0):
1 : 90.0 = (200, 0)
2 : 180.0 = (0, -200)
3 : 270.0 = (-200, 0)
4 : 360.0 = (0, 200)
1 : 30.0 = (100, 173)
2 : 60.0 = (173, 100)
3 : 90.0 = (200, 0)
4 : 120.0 = (173, -100)
5 : 150.0 = (100, -173)
6 : 180.0 = (0, -200)
7 : 210.0 = (-100, -173)
8 : 240.0 = (-173, -100)
9 : 270.0 = (-200, 0)
10 : 300.0 = (-173, 100)
11 : 330.0 = (-100, 173)
12 : 360.0 = (0, 200)
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011