AXOPEN

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.

 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 s’avè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 qu’avec AWT il s’avè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 l’exemple de graphique que nous allons générer.AWT JAVA Graphique Chart Exemple

 

Création du conteneur de données

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

Ici, nous avons choisi deux classes simples avec une qui contient les valeurs d’une 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 l’image

Nous allons maintenant créer une méthode Render qui à partir d’un 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 d’un buffuredImage. Comme il est nécessaire de connaitre la largeur de l’image 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 d’un 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 l’image suivante avec les graduations réalisées et l’unité (%). Pour les graduations, on peut utiliser la méthode drawline.

AWT graphique échelle

Ensuite, une fois ceci réalisé, il suffit de créer dans l’espace disponible les « rectangles ». Pour ce faire, il suffit d’utiliser 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 l’intégralité de la classe render qui permet à partir d’une classe Chart de générer l’image 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();
}