I. Introduction▲
Il existe de nombreuses bibliothèques graphique 2D libres en Java basées sur AWT/Swing :
Les seules bibliothèques graphiques basées sur SWT que je connaisse sont SWTChart et swt-xy-graph (qui utilise Draw2D).
Toutes ces bibliothèques offrent de grandes fonctionnalités et de très bons rendus mais pour avoir testé JFreeChart, une référence en matière de bibliothèque graphique 2D, et SWTChart, j'ai pu me rendre compte qu'elles ne possédaient pas de bonnes performances en affichage dynamique de données ou de signaux.
Pour vous faire une idée sur l'affichage dynamique de signaux, vous pouvez suivre ce lien qui présente, sous la forme d'une courte vidéo, l'utilisation et le réglage d'un oscilloscope. Ces appareils sont capables d'afficher des signaux qui varient rapidement dans le temps. Ce sont des outils très utilisés pour afficher et analyser des signaux en temps réel.
Il est bien sûr difficile voire impossible d'obtenir ce type de performance sur un ordinateur, mais il est par contre possible d'observer le comportement de ces bibliothèques en affichage temps réel. Pour avoir fait des tests avec JFreeChart et SWTChart, force est de constater que ces bibliothèques excellent dans l'affichage d'un ensemble de valeurs préétablies ou changeant très lentement, mais qu'elles deviennent assez poussives dès que vous souhaitez ajouter de nouvelles informations et rafraichir le graphe à une cadence régulière et relativement élevée : plusieurs dizaines de points toutes les cinquante millisecondes par exemple.
Je ne suis pas le seul à faire cette constatation et une petite recherche sur internet vous montrera que cet avis est partagé. On trouve d'ailleurs dans la FAQ de JFreeChart la réponse suivante à la question : Does JFreeChart support real-time charting ? En substance, cela signifie simplement que cette bibliothèque n'a pas été développée pour faire de l'affichage temps réel.
Ce premier article vous propose donc une méthode de mesure des performances de JFreeChart en affichage temps réel. Après avoir installé et configuré les outils, nous réaliserons le développement de l'interface graphique de présentation des courbes dynamiques avant de mettre au point la méthode de mesure. Nous nous servirons de ces résultats comme référence dans le prochain article.
II. Installation et configuration des outils▲
Nous allons installer et utiliser Eclipse et JFreeChart. Nous créerons un projet Java sous Eclipse et nous le configurerons afin d'utiliser les bibliothèques SWT et JFreeChart.
II.1. Eclipse▲
Si vous ne connaissez pas du tout ce logiciel, je vous invite à suivre ce tutoriel de DVP.COM qui vous guidera dans son installation et utilisation. Vous pouvez bien sûr télécharger une version d'Eclipse plus récente, les explications contenues dans l'article n'étant pas obsolètes.
Télécharger Eclipse en suivant ce lien. Vous pouvez utiliser Eclipse IDE for Java Developers par exemple. Je vous propose de lancer Eclipse dans un répertoire de travail nommé RealTimeCharting.
Maintenant qu'Eclipse est installé, lancé et que vous êtes en perspective de développement Java ; dans le menu File, cliquer sur New -> Java Project pour créer le projet nommé RealTimeCharting. Laisser les options par défaut et cliquer sur le bouton Next.
Sélectionner alors l'onglet Libraries puis cliquer sur le bouton Add Variable...
La fenêtre ci-dessous apparait. Sélectionner la variable ECLIPSE_HOME et cliquer sur le bouton Extend...
Dans la zone de texte de la nouvelle fenêtre, taper *swt*, dérouler le répertoire plugins et sélectionner la bibliothèque SWT. Dans mon cas, cette bibliothèque se nomme org.eclipse.swt.cocoa.macosx.x86_64_3.8.0.v3833.jar, mais tout dépend de votre système d'exploitation et de la version d'Eclipse que vous utilisez.
Cliquer sur les boutons OK puis Finish pour terminer la création du projet. Nous venons d'ajouter une dépendance à la bibliothèque SWT à notre projet.
II.2. JFreeChart▲
Télécharger la version 1.0.19 de JFreeChart en suivant ce lien. Une fois le fichier décompressé, vous devriez obtenir, dans le répertoire jfreechart-1.0.19, l'arborescence présentée dans l'image ci-dessous.
Dans la suite de cet article nous allons utiliser des bibliothèques et le code source de JFreeChart. Nous allons donc importer entièrement ce répertoire dans le projet. Pour ce faire, sélectionner le projet RealTimeCharting dans la vue Package Explorer puis dans le menu File cliquer sur Import.... La fenêtre ci-dessous apparait, sélectionner l'élément File System puis cliquer sur le bouton Next.
Cliquer sur le bouton Browse... pour sélectionner le répertoire de JFreeChart et cocher ce répertoire comme le montre la figure ci-dessous. Ajouter JFreeChart au chemin par défaut pour tout importer dans ce répertoire et cliquer finalement sur le bouton Finish pour terminer l'importation.
Vous devriez obtenir le résultat présenté dans l'image de la vue Package Explorer ci-dessous.
III. Développement et tests qualitatifs▲
Nous allons, dans un premier temps, écrire le code de l'interface, c'est à dire la création du contenu de la fenêtre des graphes. Pour afficher des données en temps réel dans ces graphes, il nous faudra, dans un deuxième temps, créer une classe utilitaire chargé d'envoyer des nouvelles informations régulièrement à tous les graphes. Le but est de tester qualitativement la bibliothèque JFreeChart en affichage temps réel, nous ferons des mesures par la suite.
III.1. Création de la fenêtre des graphes▲
Cette création va se faire en deux étapes. La première consiste simplement à créer le Shell et la boucle évènementielle classique de SWT. Le code de création des graphes et des courbes de ces graphes sera traité dans la deuxième étape.
Si vous ne connaissez pas la bibliothèque SWT, je vous invite à suivre ce cours de DVP.COM.
PREMIÈRE ÉTAPE : création de la classe JFreeChartRTCTest
C'est parti pour la première étape. Sélectionner le répertoire src du projet puis dans le menu File, cliquer sur New -> Class. Donner com.developpez.realtimecharting.jfreechart comme nom de package et JFreeChartRTCTest comme nom de classe. N'oubliez pas de cocher la boîte pour ajouter la méthode main à cette nouvelle classe avant de cliquer sur le bouton Finish.
Récupérer le code présenté dans le listing ci-dessous.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
package
com.developpez.realtimecharting.jfreechart;
import
org.eclipse.swt.widgets.Display;
import
org.eclipse.swt.widgets.Shell;
public
class
JFreeChartRTCTest {
/**
*
The
application
shell
*/
private
static
Shell shell;
/**
*
The
entry
point
method
for
the
test
.
*
@param
args
:
not
used
*/
public
static
void
main
(
String[] args) {
/*
*
Create
display
and
shell.
*
Assign
application
name,
shell
title
and
size.
*/
Display.setAppName
(
"
JFreeChart
RTC
SWT
"
);
Display display =
new
Display
(
);
shell =
new
Shell
(
display);
shell.setText
(
"
JFreeChart
Real
Time
Charting
with
SWT
"
);
shell.setMaximized
(
true
);
/*
Open
the
shell
maximized
*/
shell.open
(
);
/*
SWT
display
loop
*/
while
(
!
shell.isDisposed
(
)) {
if
(
!
display.readAndDispatch
(
)) display.sleep
(
);
}
display.dispose
(
);
}
}
Vous pouvez lancer ce code en cliquant sur Run As -> Java Application dans le menu contextuel de l'éditeur de la classe JFreeChartRTCTest. Ce code ne crée pour l'instant qu'une simple fenêtre avec le titre JFreeChart Real Time Charting with SWT.
DEUXIÈME ÉTAPE : création des graphes et courbes
Nous allons maintenant ajouter les trois bibliothèques suivantes pour construire les graphes dans cette fenêtre :
- jfreechart-1.0.19.jar : bibliothèque de référence de JFreeChart
- jfreechart-1.0.19-swt.jar : bibliothèque permettant d'utiliser JFreeChart dans SWT
- jcommon-1.0.23.jar : bibliothèque utilitaire pour JFreeChart
Pour ce faire, sélectionner ces trois bibliothèques comme le montre l'image de la vue Package Explorer ci-dessous. Puis dans le menu contextuel, cliquer sur Build Path -> Add to Build Path. Ces nouvelles bibliothèques devraient maintenant être visibles dans la liste des bibliothèques référencées.
Modifier maintenant cette classe pour ajouter la création des graphes et séries. Vous pouvez directement copier le code présenté dans le listing ci-dessous. Les deux paramètres nbCharts et nbSeriesPerChart permettent de régler le nombre de graphes et le nombre de courbes dans chaque graphe. Si par exemple vous modifiez nbCharts à 4, vous aurez le résultat présenté dans l'image d'après. La méthode createChartsArea() crée l'ensemble des graphes disposés dans une disposition de type grille. Elle appelle la méthode createSeries() pour créer le nombre de courbes spécifiées dans chaque graphe.
III.2. Création de la classe de génération de signaux▲
À ce niveau du développement, rien n'est dynamique. Il nous faut maintenant ajouter un mécanisme qui permet de générer des données automatiquement et régulièrement. Ce sera le rôle de la classe GenerateData que nous allons créer dès maintenant.
La classe GenerateData
Créer une classe privée, statique et interne à la classe JFreeChartRTCTest nommée GenerateData. Cette classe étend TimerTask. Elle sera utilisée par une instance de la classe Timer afin de fournir un ensemble de valeurs à afficher à toutes les séries de tous les graphes présents dans la fenêtre de l'application. Cette mise à jour de l'affichage sera faite à une certaine cadence, spécifiée dans la méthode schedule(TimerTask task, long delay, long period) de la classe Timer. La méthode run(), responsable de cet affichage, de la classe GenerateData sera appelée à une cadence approximative de 10Hz (c.a.d. toutes les 100ms). Par défaut, la génération comporte cent échantillons d'une sinusoïde, simulant ainsi une acquisition à 1000Hz. Au bout de dix secondes, la génération de données s'arrête et l'application se ferme.
Attention !
Si L'affichage prend trop de temps, c.a.d. plus de 100ms, il serait en pratique impossible d'afficher tous les points
acquis. Dans le cas de cette simulation, tous les évènements successifs seront décalés dans le temps comme il est spécifié
dans la documentation
de la classe Timer
Voici le code de la classe GenerateData :
Attention !
Ce code ne comprend pas les "includes". Si vous copiez ce code tel quel, vous aurez des erreurs de compilation
que vous pourrez résoudre en ajoutant les includes correspondants.
Utilisation de GenerateData
Il nous faut maintenant utiliser cette classe. Quelques modifications dans la méthode main() permettent de lancer le Timer à la cadence voulue et de l'arrêter lorsque l'application se termine.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
public
static
void
main
(
String[] args) {
/*
*
Create
display
and
shell.
*
Assign
application
name,
shell
title,
size
and
layout.
*/
Display.setAppName
(
"
JFreeChart
SWT
"
);
Display display =
new
Display (
);
shell =
new
Shell (
display);
shell.setText
(
"
Real
Time
JFreeChart
SWT
"
);
shell.setMaximized
(
true
);
/*
Create
charts
area
*/
createChartsArea
(
);
/*
Open
the
shell
to
display
charts
*/
shell.open
(
);
/*
Launch
Data
timer
generation
:
generates
data
each
100ms
*/
Timer timer =
new
Timer
(
);
GenerateData generateData =
new
GenerateData
(
);
timer.schedule
(
generateData, 1000
, 100
);
/*
SWT
display
loop
*/
while
(
!
shell.isDisposed
(
)) {
if
(
!
display.readAndDispatch
(
)) display.sleep
(
);
}
display.dispose
(
);
/*
Stop
data
timer
*/
timer.cancel
(
);
}
Vous pouvez tester avec un seul graphe et une seule série dans ce graphe, l'affichage semble assez correct. Vous pouvez jouer avec les paramètres nbCharts et nbSeriesPerChart : passez par exemple à six graphes avec deux courbes par graphe. Vous constaterez que ce n'est plus du tout la même chose. Tout dépend bien évidement de votre hardware et nous sommes ici en évaluation qualitative, mais on peut déjà constater que les performances ne sont pas au rendez-vous dès qu'on augmente le nombre de graphes et de courbes. En acquisition de données, il n'est pas rare d'avoir plusieurs graphes avec éventuellement plusieurs courbes par graphe. Dans la dernière partie de cet article, nous allons modifier le code afin de faire des mesures sur les temps d'affichage.
IV. Mesures▲
Avant de mettre en place une automatisation des mesures, quelques configurations sont nécessaires.
IV.1. Configurations préalables▲
Pour réaliser des mesures de temps d'affichage de la bibliothèque JFreeChart, il nous faut modifier le code source de cette bibliothèque. Nous allons donc, dans un premier temps, créer un projet, nommé jfreechart, dans notre répertoire de travail d'Eclipse. Ce projet, une fois configuré, contiendra la code source opérationnel de JFreeChart, légèrement modifié. Nous supprimerons ensuite le lien vers la bibliothèque jfreechart-1.0.19.jar pour en ajouter un vers notre nouveau projet jfreechart avant de rajouter le code de mesure des temps d'affichage.
Création du projet Java jfreechart
- Créer un nouveau projet Java nommé jfreechart
- Copier le contenu du répertoire source de JFreeChart dans le répertoire src du projet jfreechart nouvellement créé
Vous aurez des erreurs de compilation, comme le montre l'image ci-dessous, qui seront résolues un peu plus tard.
Continuons dans la création de ce projet jfreechart :
- Créer un répertoire lib dans jfreechart
- Copier la bibliothèque jcommon-1.0.23.jar dans ce répertoire à partir du répertoire lib de RealTimeCharting
- Copier également la bibliothèque servlet.jar dans ce répertoire à partir du répertoire lib de RealTimeCharting
- Ajouter ces deux bibliothèques au Build Path de jfreechart
L'image suivante vous montre le résultat : le répertoire lib et les références aux deux bibliothèques.
Vous aurez maintenant moins d'erreurs de compilation mais des packages utilisant JavaFX posent encore problèmes. Dans notre cas, il suffit de supprimer ces trois packages : clic droit sur les trois packages sélectionnés, puis cliquer sur Delete et le tour est joué !
À ce stade, vous ne devriez plus avoir d'erreurs de compilation. Pour utiliser ce nouveau projet Java jfreechart, il nous faut maintenant enlever la référence à la bibliothèque jfreechart-1.0.19.jar dans le projet RealTimeCharting (menu contextuel sur jfreechart-1.0.19.jar dans Referenced Libraries, puis Build Path et Remove From Build Path), pour lui rajouter une référence au projet jfreechart, comme le montre l'image ci-dessous :
Vérifier que l'application fonctionne toujours.
Modification du code pour la mesure des temps d'affichage
Dans le package org.jfree.chart.plot du projet jfreechart, ouvrir la classe XYPlot. Localiser la méthode draw() pour ajouter en tout début et fin quelques instructions de mesure temporelle, comme le montre le code suivant. Il faudra également ajouter la déclaration du tableau drawTimes et son accesseur.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
private
ArrayList<
Long>
drawTimes =
new
ArrayList<
Long>
(
0
);
/**
*
Return
draw
times
:
elapsed
times
between
start
and
end
of
chart
drawing
*
*
@return
an
array
of
double
containing
elapsed
times
,
in
ms
*/
public
double
[] getDrawTimes
(
) {
double
[] values =
new
double
[drawTimes.size
(
)];
for
(
int
i =
0
; i <
values.length; i+
+
) {
values[i] =
drawTimes.get
(
i);
}
return
values;
}
/**
.
.
.
*/
@
Override
public
void
draw
(
Graphics2D g2, Rectangle2D area, Point2D anchor,
PlotState parentState, PlotRenderingInfo info) {
long
time =
System.nanoTime
(
);
...
...
drawTimes.add
(
System.nanoTime
(
) -
time);
}
Nous avons maintenant la possibilité de récupérer tous les temps d'affichage de chacun des graphes pour effectuer des calculs statistiques de type moyenne, écart type etc. Pour ce faire, modifier le code de la méthode run() de la classe GenerateData afin qu'elle effectue ces calculs avant la fermeture définitive du programme. Ce code récupère tous les tableaux drawTimes de tous les graphes et les compile dans un HasMap nommé drawTimesMap avant d'appeler la méthode statique compute() de la classe com.developpez.realtimecharting.Statistics avec, entre autres, ce HasMap en paramètre. Le listing suivant présente les modifications apportées à la méthode run().
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
...
//
Stop
when
10s
have
been
acquired
if
(
initialTime >
10
) {
cancel
(
);
//
Retrieve
all
draw
times
of
each
chart,
saving
them
in
an
HashMap
HashMap<
Integer, double
[]>
drawTimesMap =
new
HashMap<
Integer, double
[]>
(
);
for
(
int
numChart =
0
; numChart <
charts.length; numChart+
+
) {
final
ChartComposite chart =
charts[numChart];
double
[] drawTimes =
chart.getChart
(
).getXYPlot
(
).getDrawTimes
(
);
drawTimesMap.put
(
numChart, drawTimes);
}
//
Compute
some
statistics
on
these
values
Statistics.compute
(
drawTimesMap, nbCharts, nbSeriesPerChart, "
JF
"
);
//
Close
application
shell.getDisplay
(
).asyncExec
(
new
Runnable
(
) {
@
Override
public
void
run
(
) {
shell.close
(
);
}
}
);
}
...
Il faut maintenant créer la classe Statistics dans la package com.developpez.realtimechart avec la méthode statique compute(), comme le montre le code suivant :
Pour effectuer les calculs dans la méthode statique compute(), j'ai utilisé la bibliothèque Apache Commons Math que vous pouvez télécharger ici. Créer ensuite un répertoire lib dans le projet RealTimeCharting pour y copier le fichier commons-math3-3.4.1.jar. Ajouter cette bibliothèque au Build Path du projet RealTimeCharting.
Vous pouvez maintenant lancer des mesures avec différentes conditions de graphes et de courbes :
- Un graphe et une courbe : C1S1JF (JF pour JFreeChart)
- Un graphe et deux courbes : C1S2JF
- Deux graphes et une courbe : C2S1JF
- Quatre graphes et deux courbes : C4S2JF
- ...
en changeant les valeurs de nbCharts et de nbSeriesPerChart. Des résultats dans la condition C1S1 sont présentés dans l'image ci-dessous : JfreeChart met en moyenne 120ms pour afficher les données avec un écart type de 20ms.
Ces valeurs peuvent évidement changer suivant la machine que vous avez, l'essentiel étant de comparer les résultats sur la même machine. Dans le cas de l'exemple précédent, c'est un MacBook Pro, OS10.7.5, CPU 2.3GHz Intel Core i5, RAM 4Go 1.333GHz DDR3, GPU Intel HD Graphics 3000 384MB.
L'inconvénient à ce niveau de développement vient du fait qu'il est nécessaire de changer les paramètres à la main pour faire des mesures dans différentes conditions. L'étape suivante consiste à automatiser ces changements et à enregistrer les mesures dans un fichier texte.
IV.2. Automatisation des mesures▲
Pour automatiser les mesures, je vous propose de passer en paramètre de la méthode main() de la classe JFreeChartRTCTest :
- le nombre de graphes
- le nombre de courbes
- le nom du fichier de sauvegarde des mesures
- un booléen permettant de savoir si on ajoute les mesures dans un fichier existant ou si on crée ce fichier
Ajoutons le traitement de ces paramètres à la méthode main() de JFreeChartRTCTest :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
/*
*
Arguments
(if
not
null)
:
nb
chart,
nb
series
per
chart,
*
file
name
and
create
or
append
to
results
file
*/
public
static
void
main
(
String[] args) {
/*
Arguments
(if
not
null)
:
nb
chart,
nb
series
per
chart,
file
name
and
create
or
append
to
results
file
*/
if
(
args !
=
null
&
&
args.length =
=
4
) {
nbCharts =
Integer.parseInt
(
args[0
]);
nbSeriesPerChart =
Integer.parseInt
(
args[1
]);
resultFileName =
args[2
];
createResultFile =
Boolean.parseBoolean
(
args[3
]);
}
...
N'oubliez pas de rajouter les champs statiques privés resultFileName et createResultFile dans la classe JFreeChartRTCTest, en tant que chaine de caractères et booléen respectivement. Modifions maintenant la méthode compute() de la classe Statistics afin de prendre en charge l'enregistrement des mesures :
Pour terminer, créons une classe de tests AllTests dans le package com.developpez.realtimecharting qui regroupera tous les appels aux tests élémentaires :
Vous devriez obtenir le type de fichier suivant :
Nous sommes maintenant armés pour analyser les résultats en conclusion de ce premier article.
V. Conclusion▲
Les résultats qui suivent ont été obtenus sur un iMac, OS X.9.5, CPU 3.1GHz Intel Core i7, RAM 16Go 1.6GHz DDR3, GPU NVIDIA GeForce GT 750M 1024 Mo.
La figure ci-dessous vous présente le diagramme en boîte issu de ces mesures. On constate évidement que les temps d'affichage augmentent avec la complexité du graphique. La valeur médiane de ce temps, dans la condition C1S1JF, c.a.d. la condition la plus simple, est d'environ 70ms. Elle est déjà proche de la cadence à laquelle on demande un renouvellement de l'affichage. Toujours dans les cas où l'on ne considère qu'un seul graphe, la valeur médiane du temps d'affichage augmente avec le nombre de courbes : elle dépasse les 150ms pour C1S4JF ! C'est à dire que JFreeChart met souvent plus de 150ms à afficher les quatre courbes dans le graphe ! Cette analyse peut être renouvelée dans les conditions comportant plus d'un graphe : C2, C3 et C4. La valeur médiane du temps d'affichage pouvant approcher la seconde.
On peut aussi constater que la variabilité augmente également avec la complexité. Les premier et troisième quartiles s'écartent de la valeur médiane et il en est de même pour les valeurs maximales. Toujours sur le diagramme en boîte, il semble que les valeurs minimales ne varient pas, mais c'est un effet d'échelle : elles restent comprises entre 20ms et 65ms, comme le montre l'histogramme des minima.
Le dernier histogramme nous montre les valeurs moyennes en fonction des conditions. L'allure générale est identique au diagramme en boîte et les valeurs moyennes sont proches des médianes pour chaque condition. Par exemple, dans le cas C4S1JF, quatre graphes avec une seule courbe par graphe, cette valeur moyenne est d'environ 180ms ! Pourtant afficher quatre signaux, ça ne devrait pas prendre autant de temps ! JFreeChart n'est effectivement pas une solution pour faire de l'affichage temps réel.
Voici les codes R qui ont été utilisés pour obtenir ces figures :