AWT – Génération de graphique et BufferedImage

Dans cet article nous allons voir comment créer ses propres graphiques avec la librairie JAVA AWT. Nous utiliserons les bufferedImage pour générer des png en sortie.
Pierre LISERONMis à jour le 17 Juin 2014

Dans cet article nous allons voir comment créer ses propres graphiques avec la librairie JAVA AWT. Nous utiliserons les bufferedImage pour générer des png en sortie.

 AWT et la création de graphique

Il existe de nombreux outils et librairies pour réaliser des graphiques en JAVA. La plus connue est surement jFreeChart qui savère être très facile à utiliser. Néanmoins, il peut être intéressant de créer ses propres graphiques pour des besoins spécifiques surtout quavec AWT il savère être très facile de créer ses propres graphiques ou schéma. Nous allons voir ici comment créer un petit histogramme et des points.

Voici lexemple de graphique que nous allons générer. 

awt_java_graphique_chart.jpg

 

Création du conteneur de données

Dans un premier temps, il est nécessaire de construire les POJO qui vont contenir linformation nécessaire à la réalisation du graphique.

Ici, nous avons choisi deux classes simples avec une qui contient les valeurs dune série et une classe qui garde les valeurs du graphique (Echelle, nom)

La classe Item

public class HistoItem {
 public double value;
 public double min;
 public double max;
 public String label;
 public Color color;

La classe graphique

public class HistoChart {
 private List<
HistoItem> items;
 private double minScale;
 private double maxScale;
 private double stepScale;
 private String label;
 private String unit;

Le renderer de limage

Nous allons maintenant créer une méthode Render qui à partir dun histoChart comprenant les valeurs de notre graphique, va générer le png en haut.

Le première étape consiste a créer une image vide avec un fond blanc, on peut réaliser ceci en partant dun buffuredImage. Comme il est nécessaire de connaitre la largeur de limage pour la créer, on va calculer la largeur en prenant en compte la nombre de bar de notre graphique et en multipliant par une taille par défaut dun item (ici defaultItemWith 70).

Nous avons choisi ici de créer une échelle. Pour réaliser ceci, la méthode drawScale crée deux lignes (avec la méthode drawLine). Une verticale et une horizontale. Sur la verticale, nous allons créer des graduations pour faire léchelle. Il suffit ici de calculer la taille disponible et de la diviser par le nombre de « jalons » désiré. On obtient limage suivante avec les graduations réalisées et lunité (%). Pour les graduations, on peut utiliser la méthode drawline. 

awt_graphique_scale.jpg

Ensuite, une fois ceci réalisé, il suffit de créer dans lespace disponible les « rectangles ». Pour ce faire, il suffit dutiliser une méthode très pratique drawRectangle et de calculer les bonnes coordonnées et la taille du rectangle en partant en haut à gauche. En effet, drawRectangle prend en entrée le point en haut à gauche du rectangle puis une largeur et une hauteur.

Petite subtilité: Afin de centrer horizontalement le texte dans un espace, il est nécessaire de connaitre la taille que ce même tête va prendre en px. Afin de réaliser cela, il existe une méthode très simple sur getFontMetrics().getStringBounds() qui donne la taille en px du texte dans le graphique. Après il suffit de la diviser par deux pour la placer au bon endroit dans son graphique.

Ici lintégralité de la classe render qui permet à partir dune classe Chart de générer limage PNG du graphique!

public class HistoRender {

public final static int defaultItemWith = 70;
public final static int defaultHeight = 700;
public final static int marginLeft = 20;
public final static int marginButton = 20;

/**
* Render the chart in pathfile
*
* @param pChart
*/
public static void render(HistoChart pChart, String pPath) {

try {
// Calcul la taille de l’image en l’agrandissant en fonction du
// nombre d’item
int lImageWidth = pChart.getItems().size() * defaultItemWith;
lImageWidth += marginLeft;

// Création de l’image
BufferedImage lImage = new BufferedImage(lImageWidth,
defaultHeight, BufferedImage.TYPE_INT_RGB);

// Création du graphique pour écrire le titre du graphique et
// remplir le font de l’image en blanc
Graphics2D lGraphics = lImage.createGraphics();
lGraphics.setPaint(Color.WHITE);
lGraphics.fillRect(0, 0, lImage.getWidth(), lImage.getHeight());
lGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// Ecriture du titre du graphique
lGraphics.setPaint(Color.BLACK);
Font lFont = new Font(Font.SANS_SERIF, Font.PLAIN, 30);
lGraphics.setFont(lFont);
lGraphics.drawString(pChart.getLabel(), 100, 30);

// Création des échelles
drawScale(lImage, pChart);
// Création des séries
drawSeries(lImage, pChart);
// Ecriture de l’image
ImageIO.write(lImage, "png", new File(pPath));

} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* Création des séries sur une image
*
* @param img
* @param pChart
*/
private static void drawSeries(BufferedImage img, HistoChart pChart) {
int i = 0;
// On dessigne chaque série (i permet de gérer le décalage vers la
// droite)
for (HistoItem lItem : pChart.getItems()) {
drawItem(img, lItem, i, pChart);
i++;
}

}
/**
* On dessine une série
*
* @param pImage
* l’image
* @param pItem
* la série
* @param pStepIndex
* le pas (décalage vers la droite)
* @param pChart
* (le graphique)
*/
private static void drawItem(BufferedImage pImage, HistoItem pItem,
int pStepIndex, HistoChart pChart) {
Graphics2D lGraphique = pImage.createGraphics();
lGraphique.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
lGraphique.setColor(Color.BLACK);
// Création de la séparation de l’échelle en bas
lGraphique.drawLine(10 + marginLeft + pStepIndex * defaultItemWith,
pImage.getHeight() - 5 - marginButton, 10 + marginLeft
+ pStepIndex * defaultItemWith, pImage.getHeight() - 10
- marginButton);
// Calcul du milieu de la série
int xmiddle = (10 + marginLeft + pStepIndex * defaultItemWith)
+ defaultItemWith / 2;
// Calcul du nombre de step dans l’échelle
int nbStep = (int) ((pChart.getMaxScale() - pChart.getMinScale()) / pChart
.getStepScale());

// Calcul de la taille d’un step de l’échelle
int stepSize = (int) ((defaultHeight - 50 - 5) / nbStep);

// On choisit la couleur du rectangle
lGraphique.setColor(pItem.color);

// Calcul de la position min du rect
double xStepRectMin = pItem.min / pChart.getStepScale();
// Calcul de la position min en px
double heightStepRectMin = xStepRectMin * stepSize;
// Calcul de la position max du rect
double xStepRectMax = pItem.max / pChart.getStepScale();
// Calcul de la position min en px
double heightStepRectMax = xStepRectMax * stepSize;
// Création du rectangle
lGraphique.fill(new Rectangle((10 + marginLeft + pStepIndex
* defaultItemWith) + 2, pImage.getHeight()
- (int) heightStepRectMax - marginButton - 10,
(int) (defaultItemWith - 4),
(int) (heightStepRectMax - heightStepRectMin)));

// On remet la couleur à noir pour écrire
lGraphique.setColor(Color.black);

// Calcul du nombre de step
double nbStepHeight = pItem.value / pChart.getStepScale();
// Calcul de la taille en px (en multipliant par la taille d’un step
double height = nbStepHeight * stepSize;

// On trace 3 lignes verticales une épaisseur de trois pixels
lGraphique.drawLine(xmiddle, pImage.getHeight() - marginButton - 10,
xmiddle, pImage.getHeight() - (int) height - marginButton - 10);
lGraphique.drawLine(xmiddle - 1,
pImage.getHeight() - marginButton - 10, xmiddle - 1,
pImage.getHeight() - (int) height - marginButton - 10);
lGraphique.drawLine(xmiddle + 1,
pImage.getHeight() - marginButton - 10, xmiddle + 1,
pImage.getHeight() - (int) height - marginButton - 10);

// On crée le petit carré en haut du trait
lGraphique.fill(new Rectangle(xmiddle - 3, pImage.getHeight()
- (int) height - marginButton - 14, 7, 7));

// On calcul la taille du texte en haut du trait pour l’aligner
// horizontalement
int stringLen = (int) lGraphique.getFontMetrics()
.getStringBounds((int) pItem.value + "", lGraphique).getWidth();
int start = defaultItemWith / 2 - stringLen / 2;
// On affiche le texte
lGraphique.drawString((int) pItem.value + "", 10 + marginLeft
+ pStepIndex * defaultItemWith + start, pImage.getHeight()
- (int) height - marginButton - 20);
// On fait de même pour le texte en bas de l’échelle
stringLen = (int) lGraphique.getFontMetrics()
.getStringBounds(pItem.label, lGraphique).getWidth();
start = defaultItemWith / 2 - stringLen / 2;

lGraphique.drawString(pItem.label, 10 + marginLeft + pStepIndex
* defaultItemWith + start, pImage.getHeight() - 5);

lGraphique.dispose();

}
private static void drawScale(BufferedImage pImage, HistoChart pChart) {
Graphics2D lGraphique = pImage.createGraphics();
lGraphique.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
lGraphique.setColor(Color.BLACK);
// Echelle verticale
lGraphique.drawLine(10 + marginLeft, 30, 10 + marginLeft,
pImage.getHeight() - 5 - marginButton);
// Echelle horizontale
lGraphique.drawLine(5 + marginLeft, pImage.getHeight() - 10
- marginButton, pImage.getWidth() - 10 + marginLeft,
pImage.getHeight() - 10 - marginButton);
// On calcul le nombre de step
int nbStep = (int) ((pChart.getMaxScale() - pChart.getMinScale()) / pChart
.getStepScale());
// On calcul la taille d’un step en px
int stepSize = (int) ((defaultHeight - 50 - 5) / nbStep);

// Pour tout les steps ou fait la graduation
for (int i = 0; i <
 nbStep + 1; i++) {
lGraphique.drawLine(5 + marginLeft, pImage.getHeight() - 10
- marginButton - (i * stepSize), 10 + marginLeft,
pImage.getHeight() - 10 - (i * stepSize) - marginButton);
lGraphique.drawString("" + (int) (i * pChart.getStepScale()), 2,
pImage.getHeight() - 10 - (i * stepSize) - marginButton);
}
// On affiche la légende
lGraphique.drawString(pChart.getUnit(), 2 + 30, pImage.getHeight() - 10
- ((nbStep) * stepSize) - marginButton);

lGraphique.dispose();
}