Delphi & Animated Flash Charts(Fusion Charts)

fusioncharts

Infosoft Global firmasının animasyonlu(Flash destekli) pek çok ürünü var. Bu ürünlerin neler olduğunu ve ne gibi yetenekleri olduğuna buradan bakabilirsiniz. Ben elbetteki hepsini anlatmayacağım. Örneğimde Fusion Widgets kütüphanesine dahil olan silindir chart’ı anlatacağım. Lâkin burada izah edeceklerim diğer chart türleri içinde geçerli olacaktır.

Hepimiz yazdığımız uygulamaların vurucu gücünün; raporlar ve bu raporların ekrandaki sunum tarzının etkileyiciliği olduğunu biliriz. Programlarımızı pazarladığımız müşterilerimiz; genellikle programın alt yapısının ne kadar güçlü olduğu, stabilitesi gibi aslında son derece önemli olan hususlarla maalesef pek ilgilenmezler. Herşeyde olduğu gibi görsellik; programlarımız için de son derece önemlidir. Görselliğin günden güne önem kazandığını Microsoft’un Windows Presentation Foundation(WPF) yada SilverLight’a verdiği önem de kanıtlıyor sanırım.

Pek çok Delphi programcısı, hazırladığı raporların görselliğini genellikle TeeChart ile sunar. TeeChart açık kaynak kodlu ve güçlü bir görsel raporlama aracı olsa da; biz bu makalemizde Flash ile hazırlanmış bir raporlama aracı olan Fusion bileşenleri ile ilgileneceğiz.

Fusion bileşenleri; Fusion Charts, Fusion Widgets, Fusion Maps gibi ayrı ayrı pek çok grafiğin desteklenebildiği zengin bir kütüphaneden oluşuyor. En önemli özelliği; client tarafında sadece Flash Player’a ihtiyaç duyması. Genel olarak, Flash Action Script dilinde yazılmış olan chart kütüphaneleri, çalışabilmek için programlama ortamında XML, HTML ve JavaScript işbirliğinden yararlanıyor. Aşağıda makalemizde izah edeceğimiz silindir chart’ın bir örneğini görebilirsiniz. Elbette, bu örneği gözlemleyebilmeniz için makinanızda Flash Player’ın yüklü olması gerekiyor. Eğer Flash Player’ınız yüklü değil ise, buradan yükleyebilirsiniz.

Silindir Chart Örneği

 

 


Fusion araçlarını Delphi’de kullanabilmek için, HTML + XML + JavaScript işbirliğinden faydalanmak gerektiğini söylemiştik. Aslında bu gerekliliği ben değil Fusion Charts söylüyor. Ben yazdığım programların bu kadar fazla gereksinime eğilimli olmasını pek sevmiyorum. Bu nedenle bizim örneğimizde HTML + XML + JavaScript ve hatta SWF dosya gereksinimi dahi olmayacak :) Elbette siz, bu fonksiyonaliteyi masaüstü uygulamalarınızda değil de, web tabanlı uygulamalarınızda kullanacaksanız o zaman gereksinimleri yerine getirmek durumunda kalacaksınız.

Makalemizde ilerlemeden evvel, ilgili siteleri gezmenizi ve örnekleri incelemenizi rica ediyorum. Böylelikle; ürün hayalgücünüzde daha net bir şekle kavuşacaktır. Ürünü satın aldığınızda, ürünle birlikte size flash dosyalarının(swf) kaynak kodlarını içeren ActionScript kodları da gelecektir(fla dosyaları). Biz bu dosyalar üzerinde birkaç değişiklik yapacağız. Yapacağımız değişikliklerin amacı; Flash ile Delphi arasında bir köprü kurma zaruretimizden kaynaklanıyor olacak. Flash üzerinde mouse ile yapacağımız tıklamaların, Delphi tarafında yakalanması ve Delphi üzerinden Flash içindeki nesnelere direkt erişim sağlamak için biraz ActionScript’e de amiyane tabir ile bulaşacağız.

İhtiyacımız olan şeyler;

  • Flash Player
  • Silindir dosyasının kaynak kodu(fla)
  • Makinanızda Flash Player’ın yüklü olduğunu varsayarak; birinci adımda Delphi içinde Flash animasyonlarını oynatabilmek için Flash Player COM nesnesini uygulamamıza ekleyeceğiz. Bunun için; Project/Import Type Library yolundan ShockWaveFlash ActiveX nesnesini projemize aşağıdaki şekilde import edeceğiz.(Install seçeneğini seçerek)

    shockwaveflash

    Bu işlemden sonra artık makinamız üzerindeki herhangi bir flash animasyonunu Delphi içinden rahatlıkla oynatabiliriz. Ancak biz, herhangi bir statik flash animasyonunun uygulamamızda oynatılmasını istemiyoruz. Biz; Flash ile yapılmış chart nesneleri ile etkileşim kurmak istiyoruz. Biz istiyoruz ki; bu flash grafiklerine istediğimiz kadar seri ekleyelim, renkleri ile oynayalım, değerlerini runtime sırasında istediğimiz gibi değiştirebilelim. Eğer, HTML + XML + JavaScript birlikteliğinden faydalanacağım derseniz; o zaman ActionScript ile yapacağımız küçük modifikasyonlara ihtiyacınız olmayacağı gibi TShockWaveFlash ActiveX’ine de ihtiyaç duymayacaksınız. Ancak bu sefer de TWebBrowser nesnesini kullanmak durumunda kalacaksınız.

    İlgili ürünleri satın aldığınızda, yada trial versiyonlarını indirdiğinizde makinanızda bu ürünlerle ilgili detaylı malümatları bulabileceksiniz. Genel olarak; Flash chart kütüphanesi; HTML içinde yazılmış olan JavaScript kodları vasıtası ile chart nesnesini oluşturuyor ve makinanızdaki bir XML dosyayı da veri kaynağı olarak kullanıp görseli hazırlıyor. Sizin tercihiniz bu yönde olacak ise; ActionScript ile ilgili kısmı atlayarak direkt kodlamaya geçebilirsiniz.

    Öncelikle, neden Action Script ile var olan flash nesnesi üzerinde değişiklik yapma ihtiyacı hissettiğimi anlatmaya çalışayım. Benim tercihimde, HTML, XML, JavaScript seçenekleri olmadığı için, elimde kalan tek seçenek Delphi üzerinde TShockWaveFlash nesnesi aracılığı ile flash chart’ı göstermekten ibaretti. Ancak bu durumda, ilgili chart nesnesinin üzerinde çalışma anında değişiklik yapma ihtiyacı bende soru işaretleri oluşturmuştu. HTML ve JavaScript ile ilgili; JavaScript kütüphaneleri sayesinde chart nesnesini oluşturabiliyor ve tüm public property’lerini kullanabiliyordum. Ancak bunu Delphi’den nasıl yapacaktım ? Örneğin; yukarıda gördüğünüz flash animasyonundaki silindirin 50 olan değerini çalışma anında nasıl arttıracak yada azaltacaktım ? Bu, sorunun birinci boyutu idi. İkinci sorun ise; bu chart nesnesinde önemli olmayan ama diğer chart nesnelerinde(column chart, line chart, area chart, pie chart vb.) son derece önemli olan mouse ile tıklama event’inin yakalanması idi. Bu özellikte; özet bilgilerin sunulduğu bir grafikten detay bir grafik raporunun çıkartılması için önemli olacaktı.

    Oysa benim elimde sadece TShockWaveFlash nesnesi vardı ve imkanlarım son derece kısıtlı idi. Bu sebepler ile ActionScript ile tanışmaya karar verdim ve son derece küçük ama bir o kadar da güçlü olan bir action script kodu ile sorunlarımın üstesinden gelebildim. Artık; Delphi’den flash içinde oluşturulmuş nesnelere ulaşabiliyor, public property’lerini istediğim değerler ile kullanabiliyordum. Ayrıca; Flash içinde yapılan mouse tıklamalarını da Delphi’de yakalayabiliyor ve hazırlamam gereken detay raporları bu sayede hazırlayabiliyordum.

    Eğer hazırsanız; Action Script’te ne gibi bir değişiklik yaptığımı göstereyim:

    actionscript_1

    Resimde gördüğünüz gibi; Yuzde isminde bir sayısal değişken tanımladık ve _root ana action script nesnesinin watch metodu ile bu değişkenin(Yuzde) her değişiminde OnYuzde isimli metodun çağrılması gerektiğini ifade ettik. OnYuzde metodunda ise; chart nesnesinin setData metodu ile yeni geçilen değerin chart’ta gösterilmesini sağladık. İşleyişte; Delphi TShockWaveFlash nesnesinin SetVariable metodu sayesinde Yuzde isimli değişkene ulaşabilecek ve değerini değiştirebilecektir. Bu değişkenin değerinin değişmesi; Chart’ın yeni vereceğimiz değer ile yeniden çizilmesi anlamına gelecektir.

    ActionScript kodumuzu yazdıktan sonra; Macromedia Flash Professional yazılımında açtığımız fla dosyamızı, Test Movie menüsü vasıtası derliyor ve swf dosyası oluşturuyoruz. Artık oluşan bu swf dosyasını Delphi altından kullanmaya hazırız. Buyrun biraz da Delphi kodu görelim:

      TCustomFusionChart = class(TComponent)
      private
        fFlash : TShockwaveFlash;
        fFileName : String;
      public
        constructor Create(const AFlash : TShockwaveFlash; const AFileName : String); virtual;
    
        function ToXML(AEncode : Boolean = true) : String; virtual;
        procedure Render; virtual;
    
        property Flash : TShockwaveFlash read fFlash;
      end;
    
      TFusionChartCylinder = class(TCustomFusionChart)
      private
        fSurfaceColor,
        fBackgroundColorFrom,
        fBackgroundColorTo,
        fFontColor : TColor;
    
        fMin,
        fMax,
        fFontSize,
        fValue  : Integer;
    
        function GetColorValues(Index : Integer) : TColor;
        procedure SetColorValues(Index : Integer; const AValue : TColor);
    
        function GetIntegerValues(Index : Integer) : Integer;
        procedure SetIntegerValues(Index : Integer; const AValue : Integer);
      public
        constructor Create(const AFlash : TShockwaveFlash; const AFileName : String); override;
        function ToXML(AEncode : Boolean = true) : String; override;
    
        property SurfaceColor         : TColor  index 0 read GetColorValues write SetColorValues;
        property BackgroundColorFrom  : TColor  index 1 read GetColorValues write SetColorValues;
        property BackgroundColorTo    : TColor  index 2 read GetColorValues write SetColorValues;
        property FontColor            : TColor  index 3 read GetColorValues write SetColorValues;
    
        property Min                  : Integer index 0 read GetIntegerValues write SetIntegerValues;
        property Max                  : Integer index 1 read GetIntegerValues write SetIntegerValues;
        property FontSize             : Integer index 2 read GetIntegerValues write SetIntegerValues;
        property Value                : Integer index 3 read GetIntegerValues write SetIntegerValues;
      end;
    
    implementation
    
    function URLEncode(const Value: String): String;
    var
      iCounter : Integer;
    begin
      Result := '';
    
      for iCounter := 1 to Length(Value) do
      begin
        if Value[iCounter] in ['A'..'Z', 'a'..'z', '0'..'9', 'ı', 'ğ', 'ü', 'ş', 'ö', 'ç', 'İ', 'Ğ', 'Ü', 'Ş', 'Ö', 'Ç', '-', '=', '&', ':', '/', '?', ';', '_', '.']
        then Result := Result + Value[iCounter]
        else Result := Result + '%' + IntToHex(Ord(Value[iCounter]), 2);
      end;
    end;
    
    function ColorToStr(const Color : TColor) : String;
    var
      R,
      G,
      B   : Byte;
      ref : TColorRef;
    begin
      ref := ColorToRGB(Color);
    
      R := GetRValue(ref);
      G := GetGValue(ref);
      B := GetBValue(ref);
    
      Result := IntToHex(R, 2) + IntToHex(G, 2) + IntToHex(B, 2);
    end;
    
    { TCustomFusionChart }
    
    constructor TCustomFusionChart.Create(const AFlash: TShockwaveFlash; const AFileName : String);
    begin
      inherited Create(nil);
    
      fFlash := AFlash;
      fFileName := AFileName;
    
      fFlash.StopPlay;
      fFlash.Movie := fFileName;
    end;
    
    function TCustomFusionChart.ToXML(AEncode : Boolean): String;
    begin
    end;
    
    procedure TCustomFusionChart.Render;
    begin
      fFlash.FlashVars := 'dataXML=' + ToXML;
      fFlash.Play;
    end;
    
    { TFusionChartCylinder }
    
    constructor TFusionChartCylinder.Create(const AFlash: TShockwaveFlash; const AFileName : String);
    begin
      inherited Create(AFlash, AFileName);
    
      fSurfaceColor        := $887766;
      fBackgroundColorFrom := $FFFFFF;
      fBackgroundColorTo   := $FFFFFF;
      fFontColor           := clBlack;
    
      fMin                 := 0;
      fMax                 := 100;
      fFontSize            := 12;
      fValue               := 0;
    end;
    
    function TFusionChartCylinder.GetColorValues(Index: Integer): TColor;
    begin
      case Index of
        0 : Result := fSurfaceColor;
        1 : Result := fBackgroundColorFrom;
        2 : Result := fBackgroundColorTo;
        3 : Result := fFontColor;
      end;
    end;
    
    procedure TFusionChartCylinder.SetColorValues(Index: Integer;
      const AValue: TColor);
    begin
      case Index of
        0 : fSurfaceColor         := AValue;
        1 : fBackgroundColorFrom  := AValue;
        2 : fBackgroundColorTo    := AValue;
        3 : fFontColor            := AValue;
      end;
    end;
    
    function TFusionChartCylinder.GetIntegerValues(Index: Integer): Integer;
    begin
      case Index of
        0 : Result := fMin;
        1 : Result := fMax;
        2 : Result := fFontSize;
        3 : Result := fValue;
      end;
    end;
    
    procedure TFusionChartCylinder.SetIntegerValues(Index: Integer;
      const AValue: Integer);
    begin
      case Index of
        0 : if (AValue < Max) then fMin := AValue;
        1 : if (AValue > Min) then fMax := AValue;
        2 : fFontSize := AValue;
        3 : begin
              fValue := AValue;
    
              if Flash <> nil then
                Flash.SetVariable('Yuzde', IntToStr(fValue));
            end;
      end;
    end;
    
    function TFusionChartCylinder.ToXML(AEncode : Boolean): String;
    const
      XML =
          '<chart bgColor="%BGCOLORFROM%,%BGCOLORTO%" bgAlpha="100,100" ' +
          'lowerLimit="%MIN%" upperLimit="%MAX%" cylFillColor="%SURFACECOLOR%" ' +
          'ticksOnRight="0" baseFont="Tahoma" baseFontColor="%FONTCOLOR%" baseFontSize="%FONTSIZE%" cylRadius="45">' +
          '<value>%VALUE%</value>' +
          '</chart>';
    begin
      Result := XML;
    
      Result := StringReplace(Result, '%BGCOLORFROM%' , ColorToStr(BackgroundColorFrom)     , [rfReplaceAll]);
      Result := StringReplace(Result, '%BGCOLORTO%'   , ColorToStr(BackgroundColorTo)       , [rfReplaceAll]);
      Result := StringReplace(Result, '%SURFACECOLOR%', ColorToStr(SurfaceColor)            , [rfReplaceAll]);
      Result := StringReplace(Result, '%FONTCOLOR%'   , ColorToStr(FontColor)               , [rfReplaceAll]);
      Result := StringReplace(Result, '%MIN%'         , IntToStr(Min)                       , [rfReplaceAll]);
      Result := StringReplace(Result, '%MAX%'         , IntToStr(Max)                       , [rfReplaceAll]);
      Result := StringReplace(Result, '%FONTSIZE%'    , IntToStr(FontSize)                  , [rfReplaceAll]);
      Result := StringReplace(Result, '%VALUE%'       , IntToStr(Value)                     , [rfReplaceAll]);
    
      if AEncode
      then Result := URLEncode(Result);
    end;
    

    Yukarıdaki kod bloğunu açıklamaya geçmeden evvel; kullanımı hakkında da kısa bir örnek vermek istiyorum müsaadeniz ile;

    var
      FusionChart : TCustomFusionChart;
    ..
    ..
    ..
    procedure TForm1.FormCreate(Sender : TObject);
    begin
      FusionChart := TFusionChartCylinder.Create(ShockwaveFlash1, 'c:\Cylinder.swf');
      with TFusionChartCylinder(FusionChart) do
      begin
        BackgroundColorFrom := clSkyBlue;
        BackgroundColorTo := clGray;
        SurfaceColor := clRed;
        Value := 50;
        Render;
      end;
    end;
    
    // SpinEdit1 TSpinEdit türünde sayısal bilgi girilebilen bir component'tir.
    procedure TForm1.btnChangeValueClick(Sender : TObject);
    begin
      if FusionChart <> nil then
        TFusionChartCylinder(FusionChart).Value := SpinEdit1.Value;
    end;
    

    Bu örneğimiz hangi değeri girerseniz o değer ile chart’ı ekranda gösterecektir. Kodumuzda bir kaç can alıcı nokta bulunmaktadır. Bunlardan en önemlisi; çalıştırılması istenen flash dosyasının(swf uzantılı dosya), TShockWaveFlash nesnesinin Movie property’sine atanmasıdır. İkinci önemli kısım; TCustomFusionChart sınıfımızın Render metodundaki FlashVars özelliğidir. TShockWaveFlash nesnesine ait olan bu özellik, flash kaynak kodunda erişime haiz global değişkenlerin değerlerini güncellemek için istifade ettiğimiz mühim bir özelliktir. Flash chart nesnesi; tasarlanış amacına uygun bir şekilde chart’ı ekranda gösterebilmek için bizden XML formatında bilgi beklemektedir. XML formatındaki bu bilgiyi virtual olarak tanımlanmış ve TCustomFusionChart sınıfını miras alan tüm sınıflarda ToXML metodunu ezerek içine yazdığımız kodlar ile sağlıyoruz. ToXML metodunun içinde tanımlı olan XML formatı, her chart’a göre değişiklik arz eden bir yapı. Fusion Charts bileşenlerini indirdiğinizde, bu formatların detayları hakkında malümata sahip olabilirsiniz.

    TFusionChartCylinder nesnesinin Value property’sinin değerinin değiştirilmesi; Flash.SetVariable(‘Yuzde’, IntToStr(fValue)); kodunu çağıracak, böylece ActionScript içinde tanımladığımız Yuzde ismindeki değişkenin değeri değişmiş olacak ve bu değişim chart’ın görselliğinin güncellenmesi ile nihayete erecektir.

    Görüldüğü üzere, örneğimizde HTML + XML + JavaScript bağımlılığını hiç kullanmadık. ActionSctipt içine yazdığımız küçük kod sayesinde bu dosyalara ihtiyaç duymadan programımız içinden dinamik olarak chart değerlerini değiştirebildik. Ancak hâla bir bağımlılığımız söz konusu. Bu da swf dosyasına bağımlılık. Programlarımızı kullanacak olan kullanıcılarımızın, ihtiyaç duyduğumuz dosyaları silmesi ve dolayısı ile programlarımızın hatalı çalışmasının önüne geçilmesi adına bağımlılıkları minimize etmekte büyük yarar olduğu kanaatindeyim. Peki ya ihtiyacımız olan swf dosyası bir şekilde silinirse ?

    O zaman raporlama aracımız hizmet veremeyecektir. Peki bundan kurtulmanın yolu nedir diye sorulursa eğer, sizde benim gibi, SWF dosyasını bir resource olarak Delphi içine derleyebilir ve bu dosyanın aradığınız klasörde olmaması durumunda resource’dan okuyup dışarı çıkartabilirsiniz. Yada yine benim yaptığım gibi, resource’da sakladığınız swf dosyasını dışarı çıkartmaz; bir stream olarak TShockWaveFlash nesnesi içine yükleyebilirsiniz ;) Bu kısımlarını siz değerli okuyucularımın araştırmalarına bırakmak istiyorum. Ancak nasıl yapılacağı hususunda bir malümat bulamaz iseniz, sizlere yardımcı olmaktan da mutluluk duyarım.

    Son bir eklenti yapmak istiyorum. O da column chart, pie chart, area chart gibi chart nesnelerindeki mouse tıklamalarının Delphi tarafından nasıl yakalacağı hususunda olacak. Bunun için de yine ActionScript’ten istifade edeceğiz. ActionScript’te fscommand isminde bir metod var. Bu metod, flash ile dış dünyayı haberdar etmek için tasarlanmış bir metod. Flash içinde fsCommand(“Selam”, “Delphi”); şeklinde bir çağrım yapılması; Delphi tarafında TShockWaveFlash nesnesinin OnFsCommand event’inin tetiklenmesi ile sonuçlanacaktır. Örneğin:

    procedure TForm1.ShockwaveFlash1FSCommand(ASender: TObject; const command,
      args: WideString);
    begin
      Memo1.Lines.Add('Command:' + command + '-> args:' + args);
      // command = Selam
      // args = Delphi
    end;
    

    Not: Bir ipucu olarak belirtmek isterim ki; Fusion kütüphanesi chart nesneleri üzerindeki mouse tıklamalarını Delphi’den yakalayabilmek için Utils.as dosyası içindeki invokeLink isimli metoda aşağıdaki gibi bir kod yazmalı ve fla dosyalarını yeniden derleyerek swf dosyası haline getirmelisiniz:

    if (strLink.charAt(0).toUpperCase() == "D" && strLink.charAt(1).toUpperCase() == "E" && strLink.charAt(2).toUpperCase() == "L" && strLink.charAt(3).toUpperCase() == "P" && strLink.charAt(4).toUpperCase() == "H" && strLink.charAt(5).toUpperCase() == "I" )
    {
      fscommand(strLink, "");
    }
    

    Pie Chart, Column Chart vb chart nesneleri için XML dosya oluştururken link isimli bir parametreye değer olarak benim yaptığım gibi DELPHI geçerseniz ve yukarıdaki kodu da ilgili yere yazıp fla dosyasını derlerseniz, Flash içindeki mouse tıklamalarını da Delphi altından rahatlıkla yakalayabilirsiniz ;)

    Saygılar, sevgiler..