понедельник, 7 апреля 2014 г.

Как заставить TPath рисовать график

Задача простая - отобразить простой график. TPath - вот идеальный кандидат! Стоит ему только дать точек, и он сам всё прекрасно отмасштабирует! Ура, ура, ура!

Но оказалось, что ...

Вот простой контрол, который вы можете бросить себе на форму:

object Path1: TPath
  Align = alClient
  Data.Path = {
    0700000000000000000000009A99193E010000000000803FC3F5A83E01000000
    00000040AE47E13E0100000000004040000000000100000000008040CDCC4C3F
    010000000000A040000000C001000000000040410000A040}
  Fill.Kind = bkNone
  HitTest = False
  RotationAngle = 180.000000000000000000
  Stroke.Color = claRed
  Stroke.Thickness = 5.000000000000000000
  Stroke.Cap = scRound
  Stroke.Join = sjRound
end
 
Я задал в Инспекторе несколько точек, изобразив некий, как мне кажется, красивый график. Здорово? Великолепно! Но, не забудьте, что ордината ( ось Y ) направлена вниз. Поэтому координаты по Y следует задавать в отрицательном виде. Мне в моей программе  показалось удобнее задавать в минусах абсциссу. Поэтому я развернул контрол на 180 градусов и минусую координаты X. Вот как это происходит:

var
  PathMax   : Single;
  PathMin   : Single;
  I         : Integer;
  P         : TPointF;
begin
 ...
    Path1.Data.Clear;
    P :=  LI2PointF( 0 );
 ...
PathMax := P.Y; PathMin := PathMax;   with Path1.Data do begin MoveTo( P ); for I := 1 to List1.Count - 1 do begin P := LI2PointF( I ); if P.Y > PathMax then PathMax := P.Y else if P.Y < PathMin then PathMin := P.Y; LineTo( P ); end; end;
 ...
Здесь LI2PointF - ф-я, возвращающая очередную точку. Причём, поделюсь страшным секретом: ордината у меня - дата TDateTime, приведённая к -Double. Да, всё так просто. Не просто только оказалось увидеть график в "бегущем" приложении. Но почему?

А что нам советуют профессионалы? Если посмотреть WebDelphi или Всеволода Леонова, то похоже, что следует использовать TPath.Data(:TPathData).Data - строку SVG. Надо сформировать скрипт типа 'M 227 239 L 328 90 L 346 250' и присвоить. И знаете что? Оно работает!

А если посмотреть в недавно вышедшую книгу Дмитрия Осипова, то кажется, что надо использовать методы MoveTo и LineTo. А я так и делал. Но стоит посмотреть на представленный в книге код по отрисовке уже заданной траектории (так можно перевести "Path"), как желание пользоваться методами начинает пропадать.

А чем плох SVG? Да собственно, нельзя сказать, что он плох. Но не кажется ли вам, что использование SVG - это вырезание гланд через анальное отверстие? Собирать скрипт - это, как ни крути - отдельная мелодия, на которую нужны таки свои инструменты - костанты команд, форматирование чисел, хранение и конкатенация строк. А потом этот свежеиспечённый скрипт придётся нашему компоненту парсить, и применять все те методы отрисовки, которые и так нам даны напрямую!

Да что же не так с этими методами? Почему они не рисуют, как полагается? Я искал какой-нибудь Begin-End или Update и нашёл ClosePath. С ним вдруг всё стало рисоваться. И даже больше, чем всё - стало рисоваться ещё линия, замыкающая последнюю и первую точки. Мне не нужна эта линия, но, ведь, остальное всё рисуется же! Что же там такого, чего нет, скажем, в AddEllipse? А там, как и в том же SetPathString, который вызывается при Data := SVG, в конце метода скромно стоит   if Assigned(FOnChanged) then    FOnChanged(Self);

Вот оно что! Ну, осталось только посмотреть подробности и вытащить метод DoChanged из protected. Всё, можно рисовать:

type
  TMyPath = class ( FMX.Objects.TPath )
//  public
//    procedure DoChanged(Sender: TObject);
  end;
...
          LineTo( P );
        end;
      end;
      TMyPath( Path1 ).DoChanged( nil );
 
      laPathMax.Text := Format( '%.3f', [ PathMax ] );
      laPathMin.Text := Format( '%.3f', [ PathMin ] );
  end;
...