Le dessin d'un composant utilise différente formes de base : lignes, rectangles, polygones.
Le but de cette section est de dessiner des étoiles de cette forme :
Afin d'avoir un composant générique permettant d'afficher le texte sous une série d'étoiles correspondant à un niveau d'appréciation, un niveau de qualité, ...
Composant.
La classe codice_1 ci-dessous définit le composant : état courant et couleurs utilisées.
Elle sera complétée progressivement au cours de ce chapitre.
package org.wikibooks.fr.components;
import java.awt.*;
import javax.swing.*;
 * Afficher un niveau avec une série d'étoiles.
 * @author fr.wikibooks.org
public class DisplayLevel extends JComponent
 // Couleurs prédéfinies
 protected static final Color
 C_LINE = Color.BLACK,
 C_LINE_OFF = Color.LIGHT_GRAY,
 C_TEXT = Color.BLACK,
 C_FILL = new Color(240,176,20);
 protected int
 maxlevel, // Le nombre total d'étoiles.
 level; // Le nombre d'étoiles pleines.
 protected String
 level_name; // Texte de niveau d'appréciation/qualité/...
 // Couleurs utilisées
 protected Color
 c_line = C_LINE,
 c_line_off = C_LINE_OFF,
 c_text = C_TEXT;
 public DisplayLevel()
 setForeground(C_FILL);
 setMinimumSize(new Dimension(400,50));
 setPreferredSize(new Dimension(500,50));
 setSize(new Dimension(500,50));
 * Modifier le niveau affiché.
 * @param level Niveau affiché.
 * @param max Niveau max.
 * @param name Texte affiché.
 public void setLevel(int level, int max, String name)
 this.maxlevel = max;
 this.level = level;
 this.level_name = name;
 repaint();
Dessin d'une étoile.
Les lignes de la forme n'étant pas courbées, le dessin d'un polygone sera utilisé.
Le polygone sera composé de 10 points :
La distance r représente le rayon du cercle englobant qui déterminera la taille des étoiles dessinées.
La distance s est calculable à partir du rayon r par la formule suivante :
formula_1
Avec formula_2
Les points étant radialement espacés régulièrement, l'angle entre deux points est de formula_3 degrés.
Coordonnées du polygone.
Les coordonnées des points du polygone sont pré-calculées et stockées dans un tableau pour un rayon de 10000 pixels.
Ces coordonnées de base servent au calcul des coordonnées selon la taille d'étoile voulue, et sont relatives au centre du cercle situé 
 private static final int[]
 SHAPE_X={ 9511, 2245, 0, -2245, -9511, -3633, -5878, 0, 5878, 3633 },
 SHAPE_Y={ -3090, -3090, -10000, -3090, -3090, 1180, 8090, 3820, 8090, 1180 };
Un objet de la classe codice_2 est utilisé pour représenter le polygone adapté à la taille de l'étoile selon celle du composant.
Les points sont calculés en multipliant les coordonnées pré-calculées des 10 points par le rayon voulu et en divisant le résultat par 10000.
Pour les points intermédiaires, on peut aussi ajouter un coefficient pour changer la forme de l'étoile :
 private Polygon shape; // Polygone calculé pour une certaine taille, null signifiant à recalculer.
 private static final int SHAPE_NORMAL = 100; // Coefficient nominal pour la forme de l'étoile.
 private int
 shape_size = -1, // La taille courante pour le polygone shape.
 shape_x, // Abscisse du point de référence.
 shape_y, // Ordonnée du point de référence.
 star_shape = SHAPE_NORMAL; // Facteur de forme de l'étoile.
 * Modifier la forme de l'étoile dessinée.
 * @param factor_percent Facteur de forme en pourcentage :<ul>
 * <li>inférieur à 100 pour une étoile plus fine,</li>
 * <li>supérieur à 100 pour une étoile plus grosse.</li>
 * </ul>
 public void setStarShape(int factor_percent)
 if (star_shape != factor_percent)
 star_shape = factor_percent;
 shape = null; // Polygone à recalculer
 shape_size = -1; // Polygone à recalculer
 repaint();
Calcul du polygone.
Le polygone est calculé, si besoin, au moment de dessiner le composant :
 /**
 * Obtenir le polygone à utiliser pour la taille voulue.
 * Si besoin, cette méthode recalcule le polygone.
 * @param size Taille d'étoile.
 * @return Le polygone à utiliser.
 private Polygon getShape(int size)
 if (shape_size<0 || size!=shape_size)
 // Polygone à recalculer car la taille est différente :
 shape_size = size;
 // Les 11 points du poloygone fermé, le dernier étant identique au premier.
 int[] xp=new int[11], yp=new int[11];
 final int
 r = size/2, // Rayon
 shpr = r*star_shape, // Coefficient pour les points intermédiaires.
 shpmax = 10000*SHAPE_NORMAL; // Diviseur pour les points intermédiaires.
 for(int i=0 ; i<10 ; i++)
 if ((i&1)==0)
 xp[i] = SHAPE_X[i]*r/10000;
 yp[i] = SHAPE_Y[i]*r/10000;
 else
 xp[i] = SHAPE_X[i]*shpr/shpmax;
 yp[i] = SHAPE_Y[i]*shpr/shpmax;
 xp[10] = xp[0];
 yp[10] = yp[0];
 shape_x = -r;
 shape_y = -r;
 shape = null;
 shape = new Polygon(xp, yp, 11); // Polygone à onze points.
 return shape;
Les coordonnées dans le polygone codice_3 sont fixes, mais le composant a besoin de le dessiner plusieurs fois à différents endroits.
La méthode codice_4 ci-dessous déplace les points en mettant à jour le point de référence servant à calculer le vecteur de translation à appliquer :
 /**
 * Déplacer le polygone au point spécifié par translation géométrique.
 * @param x Abscisse du nouveau point de référence.
 * @param y Ordonnée du nouveau point de référence.
 private void moveShapeTo(int x, int y)
 if (x!=shape_x || y!=shape_y)
 shape.translate(x-shape_x, y-shape_y);
 shape_x = x;
 shape_y = y;
Dessiner le composant.
Il ne reste plus que le dessin du composant, utilisant plusieurs fois le polygone pour afficher les étoiles et affiche également le texte.
 @Override
 protected void paintComponent(Graphics gg)
 Dimension d = getSize();
 Graphics2D g = (Graphics2D) gg;
 if (isOpaque())
 g.setColor(getBackground());
 g.fillRect(0, 0, d.width, d.height);
 applyHintsTo(g);
 final String s = level_name;
 g.setFont(getFont());
 FontMetrics fm = g.getFontMetrics();
 int y = 2, fha = fm.getMaxAscent(), fhb = fm.getMaxDescent(),
 hb = d.height-8-(s==null?0:fha+fhb),
 x = (d.width+4-(hb+4)*maxlevel)/2;
 if (hb>4 && maxlevel>0)
 Polygon p = getShape(hb);
 for(int l=0 ; l<maxlevel ; l++)
 moveShapeTo(x, y);
 if (l<level)
 g.setColor(getForeground());
 g.fillPolygon(p);
 g.setColor(c_line);
 else g.setColor(c_line_off);
 g.drawPolygon(p);
 x += hb+4;
 if (s!=null)
 y += hb+4+fha;
 g.setColor(c_text);
 int tw = fm.stringWidth(s);
 x = (d.width-tw)/2;
 g.drawString(s, x, y);
 super.paintComponent(gg);
La méthode codice_5 ajuste la configuration pour améliorer la qualité d'affichage :
 protected static void applyHintsTo(Graphics2D g)
 g.addRenderingHints(hm_hints);
 private static HashMap<Key, Object> hm_hints = new HashMap<Key, Object>();
 static
 hm_hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
 hm_hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
 hm_hints.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
 hm_hints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
 hm_hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
 hm_hints.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
 hm_hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
Code source complet et utilisation.
Le code source complet de la classe codice_1 est ci-dessous.
package org.wikibooks.fr.components;
import java.awt.*;
import java.awt.RenderingHints.Key;
import java.util.*;
import javax.swing.*;
 * Afficher un niveau avec une série d'étoiles.
 * @author fr.wikibooks.org
public class DisplayLevel extends JComponent
 // Couleurs prédéfinies
 protected static final Color
 C_LINE = Color.BLACK,
 C_LINE_OFF = Color.LIGHT_GRAY,
 C_TEXT = Color.BLACK,
 C_FILL = new Color(240,176,20);
 protected int
 maxlevel, // Le nombre total d'étoiles.
 level; // Le nombre d'étoiles pleines.
 protected String
 level_name; // Texte de niveau d'appréciation/qualité/...
 // Couleurs utilisées
 protected Color
 c_line = C_LINE,
 c_line_off = C_LINE_OFF,
 c_text = C_TEXT;
 public DisplayLevel()
 setForeground(C_FILL);
 setMinimumSize(new Dimension(400,50));
 setPreferredSize(new Dimension(500,50));
 setSize(new Dimension(500,50));
 * Modifier le niveau affiché.
 * @param level Niveau affiché.
 * @param max Niveau max.
 * @param name Texte affiché.
 public void setLevel(int level, int max, String name)
 this.maxlevel = max;
 this.level = level;
 this.level_name = name;
 repaint();
 private static final int[]
 SHAPE_X={ 9511, 2245, 0, -2245, -9511, -3633, -5878, 0, 5878, 3633 },
 SHAPE_Y={ -3090, -3090, -10000, -3090, -3090, 1180, 8090, 3820, 8090, 1180 };
 private Polygon shape; // Polygone calculé pour une certaine taille, null signifiant à recalculer.
 private static final int SHAPE_NORMAL = 100; // Coefficient nominal pour la forme de l'étoile.
 private int
 shape_size = -1, // La taille courante pour le polygone shape.
 shape_x, // Abscisse du point de référence.
 shape_y, // Ordonnée du point de référence.
 star_shape = SHAPE_NORMAL; // Facteur de forme de l'étoile.
 * Modifier la forme de l'étoile dessinée.
 * @param factor_percent Facteur de forme en pourcentage :<ul>
 * <li>inférieur à 100 pour une étoile plus fine,</li>
 * <li>supérieur à 100 pour une étoile plus grosse.</li>
 * </ul>
 public void setStarShape(int factor_percent)
 if (star_shape != factor_percent)
 star_shape = factor_percent;
 shape = null; // Polygone à recalculer
 shape_size = -1; // Polygone à recalculer
 repaint();
 * Obtenir le polygone à utiliser pour la taille voulue.
 * Si besoin, cette méthode recalcule le polygone.
 * @param size Taille d'étoile.
 * @return Le polygone à utiliser.
 private Polygon getShape(int size)
 if (shape_size<0 || size!=shape_size)
 // Polygone à recalculer car la taille est différente :
 shape_size = size;
 // Les 11 points du poloygone fermé, le dernier étant identique au premier.
 int[] xp=new int[11], yp=new int[11];
 final int
 r = size/2, // Rayon
 shpr = r*star_shape, // Coefficient pour les points intermédiaires.
 shpmax = 10000*SHAPE_NORMAL; // Diviseur pour les points intermédiaires.
 for(int i=0 ; i<10 ; i++)
 if ((i&1)==0)
 xp[i] = SHAPE_X[i]*r/10000;
 yp[i] = SHAPE_Y[i]*r/10000;
 else
 xp[i] = SHAPE_X[i]*shpr/shpmax;
 yp[i] = SHAPE_Y[i]*shpr/shpmax;
 xp[10] = xp[0];
 yp[10] = yp[0];
 shape_x = -r;
 shape_y = -r;
 shape = null;
 shape = new Polygon(xp, yp, 11); // Polygone à onze points.
 return shape;
 * Déplacer le polygone au point spécifié par translation géométrique.
 * @param x Abscisse du nouveau point de référence.
 * @param y Ordonnée du nouveau point de référence.
 private void moveShapeTo(int x, int y)
 if (x!=shape_x || y!=shape_y)
 shape.translate(x-shape_x, y-shape_y);
 shape_x = x;
 shape_y = y;
 @Override
 protected void paintComponent(Graphics gg)
 Dimension d = getSize();
 Graphics2D g = (Graphics2D) gg;
 if (isOpaque())
 g.setColor(getBackground());
 g.fillRect(0, 0, d.width, d.height);
 applyHintsTo(g);
 final String s = level_name;
 g.setFont(getFont());
 FontMetrics fm = g.getFontMetrics();
 int y = 2, fha = fm.getMaxAscent(), fhb = fm.getMaxDescent(),
 hb = d.height-8-(s==null?0:fha+fhb),
 x = (d.width+4-(hb+4)*maxlevel)/2;
 if (hb>4 && maxlevel>0)
 Polygon p = getShape(hb);
 for(int l=0 ; l<maxlevel ; l++)
 moveShapeTo(x, y);
 if (l<level)
 g.setColor(getForeground());
 g.fillPolygon(p);
 g.setColor(c_line);
 else g.setColor(c_line_off);
 g.drawPolygon(p);
 x += hb+4;
 if (s!=null)
 y += hb+4+fha;
 g.setColor(c_text);
 int tw = fm.stringWidth(s);
 x = (d.width-tw)/2;
 g.drawString(s, x, y);
 super.paintComponent(gg);
 protected static void applyHintsTo(Graphics2D g)
 g.addRenderingHints(hm_hints);
 private static HashMap<Key, Object> hm_hints = new HashMap<Key, Object>();
 static
 hm_hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
 hm_hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
 hm_hints.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
 hm_hints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
 hm_hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
 hm_hints.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
 hm_hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
Le programme ci-dessous illustre comment le composant peut être utilisé.
package org.wikibooks.fr.test;
import org.wikibooks.fr.components.*;
import java.awt.*;
import javax.swing.*;
 *
 * @author fr.wikibooks.org
public class DisplayTest
 public static void main(String[] args)
 JFrame f = new JFrame("Test");
 f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 f.setSize(500, 140);
 Container c = f.getContentPane();
 DisplayLevel niveau = new DisplayLevel();
 niveau.setSize(400, 100);
 c.add(niveau);
 niveau.setStarShape(120);
 niveau.setOpaque(true);
 niveau.setBackground(Color.WHITE);
 niveau.setFont(new Font("Dialog", Font.BOLD, 16));
 niveau.setLevel(3, 5, "Ce chapitre est bien"); // 3 étoiles sur 5.
 f.setVisible(true);