IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Partie III : Créer un composant graphique


précédentsommairesuivant

V. Propriétés de TCircleChart

Nous définirons cinq propriétés dans TCircleChart :

Nom

Type

Description

Spoke

integer

Rayon du disque

Brush

TBrush

Brush du fond du disque (sous les quartiers)

Pen

TPen

Pen utilisé pour les traits (de circonférence et de séparation entre deux quartiers)

Quarters

TChartQuarters

Collection des différents quartiers

BaseAngle

integer

Angle (en degrés) à partir duquel dessiner les quartiers (0 = à droite ; 90 = en haut)

Ces cinq propriétés doivent avoir une méthode d'accès en écriture puisqu'un changement implique qu'il faille redessiner le contrôle. Comme tout à l'heure, nous ajoutons une méthode GraphicsChange pour les modifications de Brush et de Pen.

 
Sélectionnez
private
  FSpoke : integer;
  FBrush : TBrush;
  FPen : TPen;
  FQuarters : TChartQuarters;
  FBaseAngle : integer;

  procedure SetSpoke(New : integer);
  procedure SetBrush(New : TBrush);
  procedure SetPen(New : TPen);
  procedure SetQuarters(New : TChartQuarters);
  procedure SetBaseAngle(New : integer);
  procedure GraphicsChange(Sender : TObject);

Voici les déclarations publiées de ces cinq propriétés :

 
Sélectionnez
property Spoke : integer read FSpoke write SetSpoke default 100;
property Brush : TBrush read FBrush write SetBrush;
property Pen : TPen read FPen write SetPen;
property Quarters : TChartQuarters read FQuarters write SetQuarters;
property BaseAngle : integer read FBaseAngle write SetBaseAngle default 90;

L'implémentation des six méthodes est triviale, excepté SetBaseAngle qui s'assure que la nouvelle valeur est comprise entre 0 (inclus) et 360 (exclus). À cause de l'implémentation non mathématique de l'opérateur mod, nous sommes obligés d'ajouter 360 au résultat s'il est négatif.

 
Sélectionnez
procedure TCircleChart.SetSpoke(New : integer);
begin
  if New <= 0 then exit;
  FSpoke := New;
  Invalidate;
end;

procedure TCircleChart.SetBrush(New : TBrush);
begin
  FBrush.Assign(New);
end;

procedure TCircleChart.SetPen(New : TPen);
begin
  FPen.Assign(New);
end;

procedure TCircleChart.SetQuarters(New : TChartQuarters);
begin
  FQuarters.Assign(New);
end;

procedure TCircleChart.SetBaseAngle(New : integer);
begin
  FBaseAngle := New mod 360;
  if FBaseAngle < 0 then inc(FBaseAngle, 360);
  Invalidate;
end;

procedure TCircleChart.GraphicsChange(Sender : TObject);
begin
  Invalidate;
end;

Finalement, voici l'implémentation des constructeur et destructeur pour initialiser et finaliser ces propriétés. Notez également l'affectation de clNone à la propriété Color que nous offre TControl.

 
Sélectionnez
constructor TCircleChart.Create(AOwner : TComponent);
begin
  inherited;
  Color := clNone;
  FSpoke := 100;
  FBrush := TBrush.Create;
  FBrush.OnChange := GraphicsChange;
  FPen := TPen.Create;
  FPen.OnChange := GraphicsChange;
  FQuarters := TChartQuarters.Create(Self);
  FBaseAngle := 90;
end;

destructor TCircleChart.Destroy;
begin
  FQuarters.Free;
  FPen.Free;
  FBrush.Free;
  inherited;
end;

Nous avons maintenant tous les outils en main pour dessiner les différents quartiers.

VI. Dessiner les quartiers

Maintenant que nous pouvons connaître le rayon, la couleur de fond, les quartiers, et autres informations, nous pouvons nous lancer dans l'écriture plus avancée de la méthode Paint, censée dessiner complètement le composant.

VI-A. Un peu d'algorithmique

Comme l'algorithme utilisé pour dessiner les quartiers n'est pas trivial, nous allons d'abord faire un peu d'algorithmique pour le rédiger.

Commençons par définir les variables dont nous aurons besoin :

Tout d'abord, nous allons calculer les coordonnées du centre du cercle ainsi que le TRect du carré circonscrit :

 
Sélectionnez
soit Center de type TPoint <- centre du rectangle client du contrôle;
soit CircRect de type TRect <- carré de côté 2*Spoke centré sur Center;

Ces deux données, avec les propriétés, permettront de dessiner relativement simplement le contrôle.

Ensuite, nous devons dessiner le disque de fond, sauf si Color vaut clNone :

 
Sélectionnez
si Color est différent de clNone :
  dessiner une Ellipse pleine de couleur Color sans bord dans CircRect;
finsi;

Il faut maintenant dessiner le disque de milieu de plan avec les informations des propriétés Brush et Pen.

 
Sélectionnez
dessiner une Ellipse dans CircRect avec les informations
  de pinceau et de crayon données par Brush et Pen;

Le fond est maintenant dessiné, nous allons donc dessiner les différents quartiers.

Pour cela, nous aurons besoin de quelques variables supplémentaires :

 
Sélectionnez
// quartier courantsoit Quarter de type TChartQuarter;
// informations de graphismes du quartier courant dans son état soit Graphics de type TChartQuarterGraphics;
// angle de départ, de milieu et de fin du quartier soit MinAngle, MidAngle et MaxAngle de type Single;
// points sur la circonférence correspondant aux angles soit MinPt, MidPt et MaxPt de type TPoint;
// point sur lequel centrer le texte soit TextPos de type TPoint;
// le texte à afficher dans le quartier soit Text de type string;

Les trois angles sont des valeurs d'angles absolues en radians à partir de l'angle 0 dans le cercle trigonométrique, soit à droite. MinAngle est la valeur de l'angle au départ du quartier, MaxAngle à l'extrémité, et MidAngle au milieu des deux. Les trois points sont les points correspondants à ces angles dans le cercle dessiné par notre composant.

MinAngle sera à chaque fois le MaxAngle du quartier précédent, sauf la première fois. Pour simplifier les calculs et ne pas ajouter de tests inutiles, nous initialisons donc MaxAngle à la valeur de départ, qui est définie par BaseAngle (attention : il faut transformer la valeur en radians).

 
Sélectionnez
MaxAngle <- DegreeToRadian(BaseAngle);

Ensuite nous pouvons faire notre itération sur les quartiers :

 
Sélectionnez
pour chaque Quarter dans Quarters :
  ...
finpour;

D'abord nous testons si la valeur de ce quartier ne vaut pas 0, auquel cas il ne faut pas effectuer le traitement, parce que cela serait cause d'erreurs (le dessin du quartier en lui-même faisant alors les 360° au lieu de 0) :

 
Sélectionnez
pour chaque Quarter dans Quarters :
  si Quarter.Percent vaut 0 :
    passer à l'occurrence suivante;
  finsi;
finpour;

Ensuite, on récupère les informations de graphismes en fonction de l'état (normal, abaissé ou désactivé) du quartier :

 
Sélectionnez
pour chaque Quarter dans Quarters :
  ...
  si Quarter est désactivé :
    Graphics <- Quarter.DisabledGraphics;
  sinon si Quarter est abaissé :
    Graphics <- Quarter.DownGraphics;
  sinon :
    Graphics <- Quarter.Graphics;
  finsi;
finpour;

À présent, il faut calculer les nouvelles valeurs des trois angles. Comme nous l'avons dit plus haut, MinAngle est simplement le MaxAngle de l'angle précédent. MaxAngle, lui, est le nouveau MinAngle plus la valeur en radians du quartier (connus en pour cent par la propriété Percent). Finalement, MidAngle est la moyenne des deux.

 
Sélectionnez
pour chaque Quarter dans Quarters :
  ...
  MinAngle <- MaxAngle;
  MaxAngle <- MinAngle + PercentToRadian(Quarter.Percent);
  MidAngle <- Moyenne(MinAngle, MaxAngle);
finpour;

Voici maintenant la partie mathématique de l'histoire : calculer les coordonnées des points correspondants à ces angles sur la circonférence de notre disque. En imaginant qu'on veuille le savoir pour le cercle trigonométrique, on peut utiliser les valeurs trigonométriques (cosinus et sinus) de l'angle directement comme abscisse et ordonnée. Ensuite, il suffit de multiplier les valeurs obtenues par la longueur du rayon, puisque, finalement, notre cercle n'est qu'un agrandissement du cercle trigonométrique avec comme facteur la longueur du rayon. Il faut encore arrondir ces valeurs à l'entier le plus proche pour pouvoir les enregistrer dans un TPoint.

L'ordonnée que nous obtenons alors est une ordonnée mathématique, alors que nous avons besoin de l'ordonnée informatique pour pouvoir situer le point sur le canevas. C'est pourquoi nous retranchons cette valeur à la hauteur de notre contrôle.

 
Sélectionnez
pour chaque Quarter dans Quarters :
  ...
  MinPt <- Point(Round(Spoke * Cos(MinAngle)) + Center.X,
                 Height - Round(Spoke * Sin(MinAngle)) - Center.Y);
  MidPt <- Point(Round(Spoke * Cos(MidAngle)) + Center.X,
                 Height - Round(Spoke * Sin(MidAngle)) - Center.Y);
  MaxPt <- Point(Round(Spoke * Cos(MaxAngle)) + Center.X,
                 Height - Round(Spoke * Sin(MaxAngle)) - Center.Y);
finpour;

Nous pouvons maintenant dessiner le quartier, avec la partie BackgroundBrush de Graphics.

 
Sélectionnez
pour chaque Quarter dans Quarters :
  ...
  dessiner un Quartier d'Ellipse dans CircRect à partir de MinPt jusque
    MaxPt avec les informations de pinceau de Graphics.BackgroundBrush;
finpour;

Finalement, il faut dessiner le texte avec la fonte définie par Graphics.Font et un fond défini par Graphics.FontBrush, à moins que ShowText ne vaille False. Nous centrerons le texte sur le point au milieu de Center et MidPoint.

 
Sélectionnez
pour chaque Quarter dans Quarters :
  ...
  si Quarter.ShowText :
    TextPos <- point au milieu de Center et MidPt;
    si Quarter.Text est différent de '' :
      Text <- Format('%s\r\n(%f%%)', Quarter.Text, Quarter.Percent);
    sinon :
      Text <- Format('%f%%', Quarter.Percent);
    finsi;
    écrire le texte Text avec le fond Graphics.FontBrush et
      la police Graphics.Font centré sur TextPos;
  finsi;
finpour;

Voici donc l'algorithme complet. Il ne reste plus qu'à le transposer en code Delphi.

 
Sélectionnez
soit Center de type TPoint <- centre du rectangle client du contrôle;
soit CircRect de type TRect <- carré de côté 2*Spoke centré sur Center;
// quartier courantsoit Quarter de type TChartQuarter;
// informations de graphismes du quartier courant dans son état soit Graphics de type TChartQuarterGraphics;
// angle de départ, de milieu et de fin du quartier soit MinAngle, MidAngle et MaxAngle de type Single;
// points sur la circonférence correspondant aux angles soit MinPt, MidPt et MaxPt de type TPoint;
// point sur lequel centrer le textesoit TextPos de type TPoint;
// le texte à afficher dans le quartier soit Text de type string;

si Color est différent de clNone :
  dessiner une Ellipse pleine de couleur Color sans bord dans CircRect;
finsi;

dessiner une Ellipse dans CircRect avec les informations
  de pinceau et de crayon données par Brush et Pen;

MaxAngle <- DegreeToRadian(BaseAngle);
pour chaque Quarter dans Quarters :
  si Quarter.Percent vaut 0 :
    passer à l'occurrence suivante;
  finsi;

  si Quarter est désactivé :
    Graphics <- Quarter.DisabledGraphics;
  sinon si Quarter est abaissé :
    Graphics <- Quarter.DownGraphics;
  sinon :
    Graphics <- Quarter.Graphics;
  finsi;

  MinAngle <- MaxAngle;
  MaxAngle <- MinAngle + PercentToRadian(Quarter.Percent);
  MidAngle <- Moyenne(MinAngle, MaxAngle);

  MinPt <- Point(Round(Spoke * Cos(MinAngle)) + Center.X,
                 Height - Round(Spoke * Sin(MinAngle)) - Center.Y);
  MidPt <- Point(Round(Spoke * Cos(MidAngle)) + Center.X,
                 Height - Round(Spoke * Sin(MidAngle)) - Center.Y);
  MaxPt <- Point(Round(Spoke * Cos(MaxAngle)) + Center.X,
                 Height - Round(Spoke * Sin(MaxAngle)) - Center.Y);

  dessiner un Quartier d'Ellipse dans CircRect à partir de MinPt jusque MaxPt
    avec les informations de pinceau de Graphics.BackgroundBrush;

  si Quarter.ShowText :
    TextPos <- point au milieu de Center et MidPt;
    si Quarter.Text est différent de '' :
      Text <- Format('%s\r\n(%f%%)', Quarter.Text, Quarter.Percent);
    sinon :
      Text <- Format('%f%%', Quarter.Percent);
    finsi;
    écrire le texte Text avec le fond Graphics.FontBrush et
      la police Graphics.Font centré sur TextPos;
  finsi;
finpour;

VI-B. Le code Delphi

Maintenant que notre algorithme est au point, nous pouvons le transposer en Delphi. Reprenons donc la méthode Paint et vidons-la de son contenu actuel (nous avions mis un petit code juste pour ne pas la laisser vide).

 
Sélectionnez
procedure TCircleChart.Paint;
begin
end;

Nous allons maintenant suivre l'algo pas à pas et le transposer. Commençons par les variables.

 
Sélectionnez
soit Center de type TPoint <- centre du rectangle client du contrôle;
soit CircRect de type TRect <- carré de côté 2*Spoke centré sur Center;
// quartier courantsoit Quarter de type TChartQuarter;
// informations de graphismes du quartier courant dans son étatsoit Graphics de type TChartQuarterGraphics;
// angle de départ, de milieu et de fin du quartier soit MinAngle, MidAngle et MaxAngle de type Single;
// points sur la circonférence correspondant aux angles soit MinPt, MidPt et MaxPt de type TPoint;
// point sur lequel centrer le texte soit TextPos de type TPoint;
// le texte à afficher dans le quartie rsoit Text de type string;

À cela nous ajouterons une variable I de type integer, dont nous aurons besoin pour implémenter la boucle, ainsi qu'une variable DrawTextRect de type TRect, qui servira lors du calcul de la position du texte, qui n'est pas trivial à coder en Delphi.

 
Sélectionnez
procedure TCircleChart.Paint;
var Center : TPoint;  // Centre du cercle
    CircRect : TRect; // Carré circonscrit au cercle
    I : integer;
    Quarter : TChartQuarter;
    Graphics : TChartQuarterGraphics;
    MinAngle, MidAngle, MaxAngle : Single;
    MinPt, MidPt, MaxPt, TextPos : TPoint;
    Text : string;
    DrawTextRect : TRect;
begin
  // Calcul des données concernant le disque
  Center := Point(Width div 2, Height div 2);
  CircRect := Rect(Center.X-Spoke, Center.Y-Spoke, Center.X+Spoke, Center.Y+Spoke);
end;

Passons maintenant au dessin du fond.

 
Sélectionnez
si Color est différent de clNone :
  dessiner une Ellipse pleine de couleur Color sans bord dans CircRect;
finsi;

dessiner une Ellipse dans CircRect avec les informations
  de pinceau et de crayon données par Brush et Pen;

Pour faciliter les choses, nous utiliserons une instruction with sur Canvas.

 
Sélectionnez
with Canvas do
begin
  // Dessin de la couleur de fond
  if Color <> clNone then
  begin
    Brush.Color := Color;
    Brush.Style := bsSolid;
    Pen.Style := psClear;
    Ellipse(CircRect);
  end;

  // Dessin du disque
  Brush.Assign(Self.Brush);
  Pen.Assign(Self.Pen);
  Ellipse(CircRect);
end;

Notez que nous avons dû utiliser Self.Brush et Self.Pen, car ces deux propriétés étaient cachées par leurs homographes de Canvas.

 
Sélectionnez
MaxAngle <- DegreeToRadian(BaseAngle);
pour chaque Quarter dans Quarters :
  ...
finpour;

L'appel à la fonction DegreeToRadian sera remplacé par le calcul correspondant en Delphi.

Pour implémenter la boucle, nous devons itérer sur la variable I plutôt que sur Quarter à cause de la façon de coder les tableaux en Delphi.

 
Sélectionnez
// Dessin des différents quartiers
MaxAngle := FBaseAngle * Pi / 180;
for I := 0 to Quarters.Count-1 do
begin
  Quarter := Quarters[I];
end;
 
Sélectionnez
si Quarter.Percent vaut 0 :
  passer à l'occurrence suivante;
finsi;

si Quarter est désactivé :
  Graphics <- Quarter.DisabledGraphics;
sinon si Quarter est abaissé :
  Graphics <- Quarter.DownGraphics;
sinon :
  Graphics <- Quarter.Graphics;
finsi;

Pour ce passage la traduction est littérale :

 
Sélectionnez
if Quarter.Percent = 0.0 then Continue;
if not Quarter.Enabled then
  Graphics := Quarter.DisabledGraphics
else if Quarter.Down then
  Graphics := Quarter.DownGraphics
else
  Graphics := Quarter.Graphics;

Nous arrivons au passage mathématique :

 
Sélectionnez
MinAngle <- MaxAngle;
MaxAngle <- MinAngle + PercentToRadian(Quarter.Percent);
MidAngle <- Moyenne(MinAngle, MaxAngle);

MinPt <- Point(Round(Spoke * Cos(MinAngle)) + Center.X,
               Height - Round(Spoke * Sin(MinAngle)) - Center.Y);
MidPt <- Point(Round(Spoke * Cos(MidAngle)) + Center.X,
               Height - Round(Spoke * Sin(MidAngle)) - Center.Y);
MaxPt <- Point(Round(Spoke * Cos(MaxAngle)) + Center.X,
               Height - Round(Spoke * Sin(MaxAngle)) - Center.Y);

Ici nous nous apercevons avec joie des extensions mathématiques de Delphi : la traduction est également littérale, mis à part le remplacement de PercentToRadian en le calcul correspondant. Notez qu'il faut rajouter l'unité Math à la clause uses pour pouvoir utiliser certaines fonctions présentées ci-dessus.

 
Sélectionnez
// Avancement des angles
MinAngle := MaxAngle;
MaxAngle := MinAngle + (Quarter.Percent * 2*Pi / 100);
MidAngle := (MinAngle + MaxAngle) / 2;

// Calcul des points
MinPt := Point(Round(Spoke * Cos(MinAngle)) + Center.X, Height - Round(Spoke * Sin(MinAngle)) - Center.Y);
MidPt := Point(Round(Spoke * Cos(MidAngle)) + Center.X, Height - Round(Spoke * Sin(MidAngle)) - Center.Y);
MaxPt := Point(Round(Spoke * Cos(MaxAngle)) + Center.X, Height - Round(Spoke * Sin(MaxAngle)) - Center.Y);

Voici le passage qui peut faire peur aux développeurs qui ne connaissent pas bien la classe TCanvas. En effet, il s'agit de dessiner un quartier de disque.

 
Sélectionnez
dessiner un Quartier d'Ellipse dans CircRect à partir de MinPt jusque MaxPt
  avec les informations de pinceau de Graphics.BackgroundBrush;

Heureusement, la méthode Pie de TCanvas nous permet de faire cela très aisément.

Voici le prototype de cette méthode :

 
Sélectionnez
procedure Pie(X1, Y1, X2, Y2, X3, Y3, X4, Y4 : integer);

Extrait de l'aide de Delphi 2005 :
Utilisez Pie pour dessiner dans l'image une figure en forme de part de tarte. Cette forme est définie par l'ellipse inscrite dans le rectangle déterminé par les points (X1,Y1) et (X2,Y2). La section dessinée est déterminée par deux lignes, partant du centre de l'ellipse jusqu'aux points (X3,Y3) et (X4,Y4).
Le contour est dessiné en utilisant Pen et la forme est remplie en utilisant Brush.

À nouveau, afin de gagner de la place et donc de la lisibilité, nous utiliserons une instruction with cette fois sur CircRect. On remarque au passage qu'il existe donc des occasions où cette instruction peut se révéler très efficace sans begin…end.

 
Sélectionnez
// Dessin du quartier
Brush.Assign(Graphics.BackgroundBrush);
with CircRect do
  Pie(Left, Top, Right, Bottom, MinPt.X, MinPt.Y, MaxPt.X, MaxPt.Y);

Il ne reste plus que l'affichage du texte :

 
Sélectionnez
si Quarter.ShowText :
  TextPos <- point au milieu de Center et MidPt;
  si Quarter.Text est différent de '' :
    Text <- Format('%s\r\n(%f%%)', Quarter.Text, Quarter.Percent);
  sinon :
    Text <- Format('%f%%', Quarter.Percent);
  finsi;
  écrire le texte Text avec le fond Graphics.FontBrush et
    la police Graphics.Font centré sur TextPos;
finsi;

Si l'implémentation des trois premières instructions est triviale, il n'en est pas de même pour l'écriture du texte. Commençons toujours par ces trois instructions :

 
Sélectionnez
// Calcul de la position du texte et affichage du texte
if Quarter.ShowText then
begin
  Brush.Assign(Graphics.TextBrush);
  Font.Assign(Graphics.Font);
  TextPos := Point((Center.X+MidPt.X) div 2, (Center.Y+MidPt.Y) div 2);
  if Quarter.Text <> '' then
    Text := Format('%s'#13#10'(%f%%)', [Quarter.Text, Quarter.Percent])
  else
    Text := Format('%f%%', [Quarter.Percent]);
  // écrire le texte
end;

Le problème de l'écriture réside dans sa dernière spécification (« centré sur TextPos »). Il faut pour cela parvenir à mesurer la largeur et la hauteur qu'occupera le texte dans la police spécifiée. Il existe bien des méthodes TextWidth et TextHeight dans TCanvas, mais celles-ci sont imprécises.

En réalité, il n'existe aucune solution avec les méthodes de la VCL. En revanche, une API permet de le faire : DrawText.

Pour utiliser cette méthode dans notre cas, il faut d'abord l'appeler avec le flag DT_CALCRECT afin de calculer les dimensions du rectangle dans lequel s'écrira le texte. Ensuite, il faut l'appeler une seconde fois sans ce flag, cette fois avec le bon rectangle, pour écrire réellement le texte.

Avant le premier appel, nous devons initialiser le rectangle. L'impératif est alors que sa largeur soit la largeur maximale que peut prendre le texte : nous utiliserons la largeur de notre contrôle.

 
Sélectionnez
DrawTextRect := Rect(0, 0, Width, 0);

Ensuite, nous appelons DrawText comme suit :

 
Sélectionnez
DrawText(Handle, PChar(Text), -1, DrawTextRect, DT_CALCRECT or DT_CENTER or DT_NOPREFIX);

Le premier paramètre est un handle de contexte de dessin (device context) : c'est le handle de notre canevas (nous sommes toujours dans le with correspondant). Le second est un pointeur vers la chaîne à écrire. Le troisième est la longueur de cette chaîne ; s'il vaut -1, la chaîne est considérée comme étant à zéro terminal, ce qui est notre cas puisque nous l'avons transtypée en PChar. Le quatrième est le rectangle dans lequel écrire le texte. Et le dernier est un ensemble de drapeaux d'options d'écriture.

Cet appel n'écrit rien, à cause du flag DT_CALCRECT. En revanche, après cet appel, les propriétés Right et Bottom de DrawTextRect auront été ajustées à la largeur et la hauteur que prendra le texte pour s'afficher.

Nous devons maintenant modifier légèrement le rectangle obtenu pour le centrer sur TextPos :

Jamais deux sans trois, voici encore une utilisation bizarre de l'instruction with, puisque nous spécifions deux objets à utiliser.

 
Sélectionnez
with TextPos, DrawTextRect do
  DrawTextRect := Rect(X - Right div 2, Y - Bottom div 2, X + Right div 2, Y + Bottom div 2);

Finalement, il ne reste plus qu'à appeler DrawText pour la deuxième fois, sans le paramètre DT_CALCRECT pour écrire effectivement le texte :

 
Sélectionnez
DrawText(Handle, PChar(Text), -1, DrawTextRect, DT_CENTER or DT_NOPREFIX);

Nous avons finalement terminé cette méthode de dessin. Nous avons pu à cette occasion découvrir plusieurs astuces de dessin, notamment l'utilisation des fonctions trigonométriques et de l'API DrawText.

Voici le code complet de cette méthode :

 
Sélectionnez
procedure TCircleChart.Paint;
var Center : TPoint;  // Centre du cercle
    CircRect : TRect; // Carré circonscrit au cercle
    I : integer;
    Quarter : TChartQuarter;
    Graphics : TChartQuarterGraphics;
    MinAngle, MidAngle, MaxAngle : Single;
    MinPt, MidPt, MaxPt, TextPos : TPoint;
    Text : string;
    DrawTextRect : TRect;
begin
  // Calcul des données concernant le disque
  Center := Point(Width div 2, Height div 2);
  CircRect := Rect(Center.X-Spoke, Center.Y-Spoke, Center.X+Spoke, Center.Y+Spoke);

  with Canvas do
  begin
    // Dessin de la couleur de fond
    if Color <> clNone then
    begin
      Brush.Color := Color;
      Brush.Style := bsSolid;
      Pen.Style := psClear;
      Ellipse(CircRect);
    end;

    // Dessin du disque
    Brush.Assign(Self.Brush);
    Pen.Assign(Self.Pen);
    Ellipse(CircRect);

    // Dessin des différents quartiers
    MaxAngle := FBaseAngle * Pi / 180;
    for I := 0 to Quarters.Count-1 do
    begin
      Quarter := Quarters[I];
      if Quarter.Percent = 0.0 then Continue;
      if not Quarter.Enabled then
        Graphics := Quarter.DisabledGraphics
      else if Quarter.Down then
        Graphics := Quarter.DownGraphics
      else
        Graphics := Quarter.Graphics;

      // Avancement des angles
      MinAngle := MaxAngle;
      MaxAngle := MinAngle + (Quarter.Percent * 2*Pi / 100);
      MidAngle := (MinAngle + MaxAngle) / 2;

      // Calcul des points
      MinPt := Point(Round(Spoke * Cos(MinAngle)) + Center.X, Height - Round(Spoke * Sin(MinAngle)) - Center.Y);
      MidPt := Point(Round(Spoke * Cos(MidAngle)) + Center.X, Height - Round(Spoke * Sin(MidAngle)) - Center.Y);
      MaxPt := Point(Round(Spoke * Cos(MaxAngle)) + Center.X, Height - Round(Spoke * Sin(MaxAngle)) - Center.Y);

      // Dessin du quartier
      Brush.Assign(Graphics.BackgroundBrush);
      with CircRect do
        Pie(Left, Top, Right, Bottom, MinPt.X, MinPt.Y, MaxPt.X, MaxPt.Y);

      // Calcul de la position du texte et affichage du texte
      if Quarter.ShowText then
      begin
        Brush.Assign(Graphics.TextBrush);
        Font.Assign(Graphics.Font);
        TextPos := Point((Center.X+MidPt.X) div 2, (Center.Y+MidPt.Y) div 2);
        if Quarter.Text <> '' then
          Text := Format('%s'#13#10'(%f%%)', [Quarter.Text, Quarter.Percent])
        else
          Text := Format('%f%%', [Quarter.Percent]);
        DrawTextRect := Rect(0, 0, Width, 0);
        DrawText(Handle, PChar(Text), -1, DrawTextRect, DT_CALCRECT or DT_CENTER or DT_NOPREFIX);
        with TextPos, DrawTextRect do
          DrawTextRect := Rect(X - Right div 2, Y - Bottom div 2, X + Right div 2, Y + Bottom div 2);
        DrawText(Handle, PChar(Text), -1, DrawTextRect, DT_CENTER or DT_NOPREFIX);
      end;
    end;
  end;
end;

précédentsommairesuivant

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2005 Sébastien Doeraene. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.