Пропустить навигацию.
Главная

Биоритмы

В нашей предыдущей книге приводился один из возможных вариантов DOS-приложения для построения кривых жизненного цикла, известных под названием биоритмы. Мы решили вернуться к этому приложению, но уже на качественно новом уровне, с целью продемонстрировать не совсем тривиальный интерфейс и процедуры настройки положения и размеров объектов, находящихся на форме.

Возможно, не все читатели знакомы с предыдущей публикацией. Поэтому вкратце напомним постановку задачи. Сторонники теории биоритмов утверждают, что каждому человеку присущи три периодических процесса, сопровождающие его жизнь с момента рождения. Первый из них с периодом 23 дня соответствует физическому состоянию, второй с периодом 28 дней — интеллектуальному, третий с периодом 33 дня — эмоциональному. По каждому процессу можно построить график, отложив по горизонтальной оси время t в днях с момента рождения, а по вертикальной оси — амплитуду синусоиды со своим периодом:

PH=sin(2*pi*t/23)

IN=sin(2*pi*t/28)

EM=sin(2*pi*t/33)

Совместив все графики на общем рисунке, можно определить моменты общего подъема или спада потенциала человека. Особого единства в трактовке «плохих» интервалов жизни человека нет. Одни авторы считают, что к «черным» дням относятся моменты, когда все три кривые одновременно или в достаточно узком интервале пересекают ось времени, другие склонны рассматривать в качестве дней депрессии моменты, когда все кривые имеют общую или близкую точку минимума. Так как графики ритмов взрослого человека содержат 15-20 тысяч точек (предполагается, что каждая точка соответствует прожитому дню), да и заниматься анализом прошедших дней великого смысла не имеет, то целесообразно рассматривать поведение знаковых кривых на текущий месяц или на период предстоящих важных событий.

Исходная информация, предоставляемая пользователем, содержит дату рождения (день — от 1 до 31, месяц — от 1 до 12 и год — от 1900 до 2025). По умолчанию графики биоритмов рассчитываются на текущий месяц, но программа должна предусматривать возможность указания какого-либо месяца любого года из указанного интервала. Естественно, что программа должна контролировать правильность задания исходных данных, выдавать корректные сообщения с указанием характера ошибки и позволять произвести повторный ввод ошибочного параметра. Кроме того, за один сеанс общения с программой пользователю надо предоставить возможность набирать неограниченное количество исходных данных.

Приложение 9_06 (VB). «Биоритмы».

С целью структурирования программы, кроме ее интерфейсной части целесообразно выделить в отдельные функции следующие процедуры:

  • Offset — определение количества дней, прошедших от 1.01.1900 г до заданной даты;

  • Axis — построение осей для графика биоритмов с нанесением рисок по горизонтальной оси и числовых меток, упрощающих определение дня текущего месяца;

  • Graphic — построение графика синусоиды y=sin(x*2*pi/T+phi) с заданными периодом T и начальным смещением по фазе phi.

В этом приложении мы будем максимально использовать простейшие интерфейсные объекты и не будем применять специальные функции обработки дат. Алгоритм процедуры Offset предлагается самый примитивный. Например, будем прибавлять к сумме по 365 дней за каждый полностью прошедший год и компенсировать добавочным днем каждый високосный. В текущем году количество дней определяем с помощью массива days, хранящего количество дней в каждом месяце. Нужно только не забыть откорректировать число дней в феврале (days[2]=29), если текущий год является високосным. В выбранном диапазоне лет проверка високосного года упрощается и сводится к проверке остатка от деления номера года на 4.

Для определения текущей даты можно воспользоваться системной функцией (переменной) Now, представляющей значение даты и времени в формате Variant, и выделять из ее значения соответствующие компоненты системными функциями Day, Month и Year.

Ввод исходной информации может быть организован разными способами, но в любом случае потребуется несколько меток и окон ввода. Сокращению набора данных, принадлежащих заданному интервалу, могут содействовать объекты типа UpDown или различные шкалы с перемещаемыми ползунками. В предлагаемом ниже варианте используются две рамки (рис. 9.6), укомплектованные следующими компонентами:

  • Frame1 — рамка, играющая роль контейнера для элементов ввода дня рождения;

  • Label1 — метка с надписью «День», расположенная слева от окна Text1 и предназначенная для индикации поля ввода дня даты рождения;

  • Text1 — текстовое поле, отображающее день рождения;

  • UpDown1 — кнопка, ассоциированная с окном ввода Text1.

Обработка календарных дат и временных интервалов

Рис. 9.6. Компоненты ввода исходных данных

Еще две тройки аналогичных компонент — (Label2, Text2, UpDown2) и (Label3, Text3, UpDown3), — предназначены для ввода и отображения месяца и года рождения. В рамке Frame2 находятся две идентичные тройки компонент — (Label4, Text4, UpDown4) и (Label5, Text5, UpDown5), используемые для задания начальной даты графика биоритмов.

При выборе компонент в среде Visual Basic и установке значений некоторых их свойств нужно учитывать следующее:

  1. Текстовые поля ввода Text1, Text2, ... должны быть связаны с соответствующими кнопками UpDown1, UpDown2, ... . Для этого у первой кнопки необходимо задать значения свойств BuddyControl=Text1 (имя ассоциированного объекта) и BuddyProperty=Text (поле ввода, в котором будет отображаться значение свойства Value — числового показателя, формируемого с помощью кнопки). Затем необходимо установить граничные значения свойства Value , ограничивающие диапазон изменения соответствующего данного (для первой кнопки Min=1, Max=31). Для оставшихся четырех кнопок должны быть выполнены аналогичные установки.

  2. Истинные размеры рамок Frame1 и Frame2, а также габариты и положение всех компонент в контейнерах целесообразно подобрать на стадии проектирования с тем, чтобы во время разворачивания формы или изменения ее габаритов во время работы приложения мы могли бы ограничиться заданием координат левого верхнего угла каждой рамки. А все внутренние объекты при этом сохранят свое положение относительно границ рамки.

  3. Имеет смысл установить содержимое окон ввода даты дня рождения на стадии проектирования (в предлагаемом варианте выбрана дата 1.01.1985). Содержимое окон Text4 и Text5 придется формировать в момент запуска программы, т.к. только тогда станут известными текущие месяц и год.

Кнопка с надписью <Построить> является сигналом к построению кривых после задания всех исходных данных.

Собственно график и координатные оси строятся в окне Picture1 — на поверхности объекта типа PictureBox. Габариты этого окна зависят от размеров главной формы, поэтому кривые биоритмов должны вписываться в рамки, размеры которых на стадии проектирования неизвестны. Для преодоления этих трудностей в программе предусмотрены глобальные переменные Xmin и Xmax, значения которых чуть меньше (на величину dxy), чем абсциссы левой и правой границ окна Picture1. Глобальная переменная Ymid отслеживает положение средней горизонтальной линии в окне Picture1. Амплитуда размаха синусоид тоже выбирается чуть меньшей, чем высота окна Picture1.

Для большей наглядности графиков форма приложения использует полный экран. Поэтому на стадии проектирования главной формы можно задать ее свойство WindowState = Maximized. Все операции по согласованию положения и размеров поля рисунка и рамок ввода сосредоточены в обработчике события Form_Resize. Все эти действия сопровождаются достаточно подробными комментариями.

Любые изменения в полях ввода перехватываются обработчиками события Text_Change. На эти процедуры управление передается при изменении хотя бы одной цифры в соответствующем окне. Основная работа этих обработчиков сводится к пересылке текущего значения из окна ввода в соответствующую глобальную переменную. В среде Visual Basic в окне ввода, ассоциированном с кнопкой UpDown, можно набирать значение явно, минуя кнопку. Поэтому введенные данные должны проверяться на корректность. По дню рождения все компоненты проверяются в момент нажатия кнопки <Построить>, а по начальной дате графика эти проверки оказалось удобнее разместить в обработчиках события Text_Change.

В процедуре построения осей следует обратить внимание на позиционирование текущей точки (CP) при построении штрихов и выборе позиции для вывода числовой метки (маркера) с помощь свойств CurrentX и CurrentY. При выводе текста с помощью метода Print текущая точка определяет координаты левого верхнего угла прямоугольника, окаймляющего надпись.

'Глобальные переменные

Dim Xmin As Integer, Xmax As Integer

'Границы по горизонтали для построения графиков

Dim Ymid As Integer 'Высота оси x

Dim dxy As Integer 'Добавка для разных отступов

Dim days(12) As Integer 'Количество дней в каждом месяце

Dim d As Integer, m As Integer, y As Integer 'Дата рождения

Dim d1 As Integer, m1 As Integer, y1 As Integer 'Дата графика

Private Sub Form_Resize()

'Настройка размеров и начального положения объектов на форме

'Установка начальных значений некоторых переменных

Dim CurDate

'Размеры и положение окна рисунка

Picture1.Left = Left + 1000

Picture1.Top = Top + 3000

Picture1.Height = Height - 4000

Picture1.Width = Width - 2000

dxy = 100

Xmin = dxy

Xmax = Picture1.Width - 2 * dxy

Ymid = Picture1.Height \ 2 - dxy

'Положение рамки дня рождения

Frame1.Left = Left + 1000

Frame1.Top = Top + 250

'Положение рамки с датой биоритмов

Frame2.Left = Frame1.Left + Frame1.Width + 1000

Frame2.Top = Top + 250

'Длительности месяцев

days(1) = 30: days(2) = 28: days(3) = 31: days(4) = 30

days(5) = 31: days(6) = 30: days(7) = 31: days(8) = 31

days(9) = 30: days(10) = 31: days(11) = 30: days(12) = 31

CurDate = Now 'Опрос текущей даты и времени

'Разделение компонент даты

d1 = Day(CurDate): m1 = Month(CurDate): y1 = Year(CurDate)

'Коррекция на случай февральской даты в високосном году

If m1 = 2 And (m1 Mod 4 = 0) Then days(2) = 29

'Начальные установки в окнах ввода

d = 1: m = 1: y = 1985

Text4.Text = Str(m1)

Text5.Text = Str(y1)

'Положение и размеры списка с информацией о цвете кривых

List1.Top = Top + 400

List1.Left = Frame2.Left + Frame2.Width + 1000

List1.Width = 4000

End Sub

Private Sub Text1_Change()

'Реакция на изменение дня даты рождения

d = Val(Text1.Text)

End Sub

Private Sub Text2_Change()

'Реакция на изменение месяца даты рождения

m = Val(Text2.Text)

End Sub

Private Sub Text3_Change()

'Реакция на изменение года даты рождения

y = Val(Text3.Text)

End Sub

Private Sub Text4_Change()

'Реакция на изменение месяца графика биоритмов

m1 = Val(Text4.Text)

If m1 < 1 Or m1 > 12 Then

MsgBox ("Неверно указан месяц. Повторите")

Text4.Text = 1

Exit Sub

End If

End Sub

Private Sub Text5_Change()

'Реакция на изменение года графика биоритмов

y1 = Val(Text5.Text)

If y1 < 1900 Or y1 > 2025 Then

MsgBox ("Неверно указан год. Повторите")

Text5.Text = 1985

Exit Sub

End If

'Коррекция на случай февральской даты в високосном году

If (y1 Mod 4) = 0 And m1 = 2 Then days(2) = 29

End Sub

Private Sub Command1_Click()

' Выполнение команды "Построить"

Dim dd As Long

Picture1.Cls 'Очистка поля рисунка

' Контроль правильности даты рождения

If m < 1 Or m > 12 Then

MsgBox ("Неверно указан месяц. Повторите"): Exit Sub

End If

If d < 1 Or d > days(m) Then

MsgBox ("Неверно указан день. Повторите"): Exit Sub

End If

If y < 1900 Or y > y1 Then

MsgBox ("Неверно указан год. Повторите"): Exit Sub

End If

Axis 'Построение и маркировка координатных осей

'Вычисление возраста в днях на начальную дату графика

dd = Offset(1, m1, y1) - Offset(d, m, y)

'Построение кривой физического состояния

Graphic 23, dd Mod 23, 4

'Построение кривой интеллектуального состояния

Graphic 28, dd Mod 28, 2

'Построение кривой эмоционального состояния

Graphic 33, dd Mod 33, 1

End Sub

Private Sub Axis()

'Построение и маркировка осей в поле рисунка

Dim X As Integer, j

'Построение оси y

Picture1.Line (dxy, dxy)-(dxy, 2 * Ymid + dxy)

'Построение оси x

Picture1.Line (dxy, Ymid)-(Xmax + dxy, Ymid)

'Цикл нанесения штрихов по оси x

For j = 1 To days(m1)

X = Xmin + j * Xmax \ 31 'Координата очередного штриха

If (j Mod 5) <> 0 Then 'Если штрих - обычный

'Построение короткого штриха

Picture1.Line (X, Ymid - dxy)-(X, Ymid + dxy)

Else 'Если штрих - удлиненный, с маркировкой

Picture1.CurrentX = X 'Положение CP по оси x

Picture1.CurrentY = Ymid - dxy * 2

'Построение удлиненного штриха

Picture1.Line -(X, Ymid + dxy * 2)

'Коррекция позиции начала маркировки

Picture1.CurrentX = X - dxy

'Нанесение маркера

Picture1.Print j

End If

Next j

End Sub

Private Sub Graphic(T As Integer, Dfi As Double, Col As Long)

'Построение очередной синусоиды

Dim X As Integer, y As Integer, j

Const TwoPi = 6.2831853

X = Xmin 'Начальная точка графика

y = Ymid * (1 - 0.9 * Sin(TwoPi * Dfi / T))

'Отображение начальной точки

Picture1.PSet (X, y), QBColor(Col)

'Цикл вычисления координат кривой в каждый день месяца

For j = 1 To days(m1)

X = Xmin + j * Xmax \ 31

y = Ymid * (1 - 0.9 * Sin(TwoPi * (j + Dfi) / T))

'Построение очередного звена синусоиды

Picture1.Line -(X, y), QBColor(Col)

Next j

End Sub

Private Function Offset(d As Integer, m As Integer, y As Integer) As Long

'Вычисление количества дней, прошедших от 1.01.1900 до указанной даты

dd = 0:

'Цикл учета полных лет

For k% = 1900 To y - 1

dd = dd + 365

'Поправка на високосный год

If (k% Mod 4) = 0 Then dd = dd + 1

Next k%

'Учет дней, прошедших от начала года y до месяца m

For k% = 1 To m - 1: dd = dd + days(k%): Next k%

Offset = dd + d: 'Учет дней, прошедших в месяце m

End Function

Приложение 9_07 (Delphi). «Биоритмы».

Ниже приводится еще один вариант приложения, воспроизводящего кривые биоритмов, реализованный в среде Delphi. Его особенность заключается в другом варианте интерфейса, использующего компоненты типа DateTimePicker, и в более широком привлечении системных функций, связанных с обработкой дат.

В приложении выбран более компактный формат объекта с DateMode=UpDown (рис. 9.7), в котором изменение каждой части даты может быть выполнено с помощью встроенной кнопки UpDown. Значение установленной даты программа извлекает из свойства Date.

В отличие от VB-приложения здесь не потребовалась процедура Offset, т.к. ее заменила системная функция DaysBetween, аргументами которой являются две даты в формате TDateTime. Две другие функции MonthOf и YearOf позволили определить числовой номер месяца и год, соответствующие начальной дате графика. Так как система автоматически вписывает текущую дату в окно объекта DateTimePicker2, а начальная дата графика должна совпадать с началом месяца, приходится корректировать номер дня с помощью функции EncodeDate. Три ее аргумента представлены обычными числовыми значениями года, месяца и дня, а результатом работы функции является дата в формате TDateTime. Перед построением очередного графика в процедуре Axis производится очистка поля рисования с помощью метода FillRect. По умолчанию он использует белый цвет кисти и сплошную заливку. Остальные части программы повторяют логику VB-приложения.

unit Unit1;

interface

uses

Windows, Messages, SysUtils, Variants, Classes,

Graphics, Controls, Forms, Dialogs, ComCtrls,

StdCtrls, ExtCtrls,DateUtils;

type

TForm1 = class(TForm)

Label1: TLabel;

Label2: TLabel;

Memo1: TMemo;

Button1: TButton;

DateTimePicker1: TDateTimePicker;

DateTimePicker2: TDateTimePicker;

Image1: TImage;

procedure Button1Click(Sender: TObject);

procedure Axis;

procedure Graphic(T:integer;Dfi:integer; col:TColor);

private

{ Private declarations }

public

{ Public declarations }

end;

var

Form1: TForm1;

Xmin,Xmax,Ymid,m1,y1:integer;

dxy:integer=5;

days:array [1..12] of byte=

(30,28,31,30,31,30,31,31,30,31,30,31);

implementation

{$R *.dfm}

procedure TForm1.Axis;

{Построение и маркировка осей}

var

j,x:integer;

begin

{Очистка поля графика}

Image1.Canvas.FillRect(Rect(0,0,Image1.Width,Image1.Height));

{Построение оси y}

Image1.Canvas.MoveTo(dxy,dxy);

Image1.Canvas.LineTo(dxy,2*Ymid-dxy);

{Построение оси x}

Image1.Canvas.MoveTo(dxy, Ymid);

Image1.Canvas.LineTo(Xmax+dxy,Ymid);

{Цикл нанесения штрихов по оси x}

for j:=1 to days[m1] do

begin

x:=Xmin + j*Xmax div 31; {Координата очередного штриха}

if (j mod 5) <> 0 then {Если штрих - обычный}

begin

{Построение короткого штриха}

Image1.Canvas.MoveTo(X,Ymid-dxy);

Image1.Canvas.LineTo(X,Ymid+dxy);

end

else {Если штрих - удлиненный, с маркировкой}

begin

{Построение удлиненного штриха}

Image1.Canvas.MoveTo(X,Ymid-dxy*2);

Image1.Canvas.LineTo(X,Ymid+dxy*2);

{Коррекция позиции начала маркировки и нанесение маркера}

Image1.Canvas.TextOut(X-dxy,Ymid+dxy*2,IntToStr(j));

end;

end;

end;

procedure TForm1.Graphic(T:integer;Dfi:integer; col:TColor);

{Построение графика}

var

x,y,j:integer;

begin

Image1.Canvas.Pen.Color:=col;

x:=Xmin; {Начальная точка графика}

y:=round(Ymid*(1-0.9*Sin(2*Pi*Dfi/T)));

{Отображение начальной точки}

Image1.Canvas.MoveTo(x,y);

{Цикл вычисления координат кривой в каждый день месяца}

for j:=1 to days[m1] do

begin

x:=Xmin + j * Xmax div 31;

y:=round(Ymid*(1-0.9*Sin(2*Pi*(j+Dfi)/T)));

{Построение очередного звена синусоиды}

Image1.Canvas.LineTo(x,y);

end;

end;

procedure TForm1.Button1Click(Sender: TObject);

{Выполнение команды "Построить"}

var

dd:integer;

begin

{ dxy:=5;}

Xmin:=dxy;

Xmax:=Image1.Width-2*dxy;

Ymid:=Image1.Height div 2;

m1:=MonthOf(DateTimePicker2.Date); {Месяц графика}

y1:=YearOf(DateTimePicker2.Date); {Год графика}

if (y1 mod 4)=0 then days[2]:=29;

{Коррекция дня в начальной дате графика}

DateTimePicker2.Date:=EncodeDate(y1,m1,1);

if (YearOf(DateTimePicker2.Date) mod 4)=0 then days[2]:=29;

{Построение и маркировка осей}

Axis;

{Определение числа дней, прошедших от момента рождения}

dd:=DaysBetween(DateTimePicker2.Date,DateTimePicker1.Date);

{Построение кривой физического состояния}

Graphic(23,dd mod 23,clRed);

{Построение кривой интеллектуального состояния}

Graphic(28,dd mod 28,clGreen);

{Построение кривой эмоционального состояния}

Graphic(33,dd mod 33,clBlue);

end;

end.

На рис. 9.7 представлен образец работы этого приложения.

Обработка календарных дат и временных интервалов

Рис. 9.7. Образец работы приложения "Биоритмы"