![]()
![]()
![]()
![]()
![]()
Next: Améliorations possibles Up: Grapheur Previous: Introduction   Contents   Index
Subsections
Réalisation La réalisation du grapheur tient en 150 lignes de code Scheme faisant appel à la bibliothèque OK présentée dans le chapitre précédent consacré aux primitives graphiques. Nous avons en premier quelques fonctions utilitaires, puis la fonction
get-valuesqui invoque un producteur pour fournir un vecteur de fonction, puis la fonction de tracéplotet enfin la fonction principalegraph.
number->integer
Nous définissons tout d'abord une fonction retournant un entier pour toute valeur numérique rationnelle ou réelle passée en argument :
; retourne un entier à partir d'un ; rationnel ou d'un réel (define (number->integer x) (cond [(integer? x) x] [(rational? x) (quotient (numerator x) (denominator x))] [(real? x) (inexact->exact x)] [(complex? x) (error "cannot convert complex")] [else (error "cannot be converted '~a'" x)]))Cette fonction sera utilisée pour convertir les valeurs réelles et rationnelles en entiers, seul type accepté par les primitives graphiques. C'est une fonction par cas sur les types de valeurs numériques traitées. On remarquera la fonction
errorqui prend comme premier argument une chaîne de caractères ayant des 'emplacements~a' qui seront remplacés par l'affichage des arguments suivants.En Scheme, les calculs sont effectués dans le mode 'le plus exact possible'. Cela signifie que si on divise 1 par 2, Scheme retourne le nombre rationnel 1/2. Ainsi, si on multiplie (1/2)*2, on obtient 1, qui est le résultat exact. Dans le cadre de ce programme, nous allons effectuer des calculs de conversion entre données numériques et coordonnées écran ; pour cela, la division va être utilisée, et Scheme va produire des nombres rationnels. Nous devrons donc utiliser
number->integerpour convertir ces nombres rationnels en entier, seul type accepté par les primitives graphiques d'OpenScheme.
get-values
Nous définissons ensuite la fonction qui est chargée de produire un vecteur de valeur à partir de la plage x souhaitée et un producteur. La fonction est définie par :
; retourne un vecteur de n valeurs (define (get-values min-x max-x step producer) (check (procedure? producer)) (let* ([n (+ (number->integer (/ (- max-x min-x) step)) 1)] [values (make-vector n)]) (do ([x min-x (+ x step)] [i 0 (+ i 1)]) ((eq? i n)) (vector-set! values i (producer x))) values))))La fonction vérifie tout d'abord que l'argument procucer est une procédure avec l'appel à
check.Checkest une forme spéciale d'OpenScheme qui permet de placer des vérifications dans les programmes. Ces vérifications peuvent être désactivées lorsque l'on exécute le programme avec un OpenScheme optimisé pour la vitesse : dans ce cas, aucune vérification n'est effectuée, procurant une vitesse d'exécution optimale. Ce mode de fonctionnement ne doit cependant être utilisé qu'avec des programmes surs en bien testé. Cette fonction est l'équivalent Scheme de la macroassertdu langage C.La fonction construit ensuite un vecteur dont la taille est déduite de la plage définie. Ce vecteur est rempli avec les valeurs produites par le producteur, invoqué pour toutes les valeurs de la plage. Ce vecteur est enfin retourné à l'appelant.
min-max
Pour placer automatiquement les axes de notre graphe, nous avons besoin d'une fonction retournant la valeur minimum et la valeur maximum du vecteur de valeurs produit par la fonction précédente. Cette fonction est définie par :
; retourne la paire formée du minium et du maximum ; parmi un vecteur de valeurs (define (min-max values) (let ([n (vector-length values)]) (let loop ([i 1] [min-y (vector-ref values 0)] [max-y (vector-ref values 0)]) (if (eq? i n) (cons min-y max-y) (loop (+ i 1) (min min-y (vector-ref values i)) (max max-y (vector-ref values i)))))))Elle est principalement bâtie autour d'une itération construite avec un
letnommé (loop).Loopdevient une fonction récursive dont les paramètres sonti,min-xetmax-y, initialisés avec les expressions1et lesvector-ref. L'itération se poursuit tant que la valeurin'est pas égale àn, avec de nouvelles valeurs pour les paramètres deloop. Lorsque la condition d'arrêt est rencontrée, on retourne simplement la paire formée de la valeur minimum et de la valeur maximum trouvée.Cette forme d'itération utilisant le
letnommé est très puissante car complètement paramétrable. Cependant, lorsqu'on le pourra, on préférera utiliser des formes plus classiques commefor-eachoudo.
get-plot-color
Pour tracer les courbes en couleur et afin de simplifier le paramétrage du logiciel, nous proposons d'utiliser une fonction retournant une couleur parmi un ensemble prédéfini, de manière cyclique. Cette fonction est réalisée de la manière suivante :
; retourne à chaque appel une couleur ; différente (define get-plot-color (let* ([pool (list ok:red ok:yellow ok:green ok:blue)] [current pool]) (lambda () (let ([color (car current)]) (set! current (cdr current)) (if (null? current) (set! current pool)) color))))L'aspect remarquable de cette définition est le
let*placé 'avant' lelambda: cette écriture permet de définir des variables globales qui ne seront accessibles que dans le corps de la fonction. Ces variables sontpool, initialisée avec une liste de couleurs, etcurrentprenant la même valeur quepool. L'utilisation dulet*permet d'initialisercurrentavecpool. Si nous avions utilisé un simplelet, la variable pool aurait été 'invisible' à l'expression initialisantcurrent.Ainsi, la 'vraie' fonction est le
lambda; la valeur depoolest decurrentsont conservées entre chaque appel àget-plot-color. Le corps de la fonction prend une couleur de la listecurrentet déplacecurrentvers l'élément suivant. Sicurrentdevient la liste vide, alors elle est initialisée à la valeur depool, c'est à dire la liste des couleurs.Ce qui est remarquable avec le langage Scheme, c'est la concision avec laquelle les programmes sont exprimés. Tout ce que l'on 'fait' en Scheme, on peut le faire avec d'autres langages, mais on le fait de manière plus concise en Scheme.
plot
Pour l'instant, nous ne définissons pas la fonction de tracé. Nous définirons cette fonction plus bas. Pour l'instant, nous avons :
(define (plot mode device min-x max-x min-y max-y list-of-values) (ok:draw-text device 10 10 "à finir !"))Les arguments de la fonction sont le mode de tracé défini par le symbole
dotouline, le device où dessiner le graphe, la valeur minimum et la valeur maximum dex, la valeur minimum et la valeur maximum deyet une liste de vecteurs contenant les valeursydes points à dessiner, vecteurs retournés par la fonctionget-values, vue plus haut.Nous écrivons un petit message en attendant de faire mieux !
create-device
La fonction
create-devicecrée une fenêtre et l'initialise :; crée et initialise la fenêtre de dessin (define (create-device proc) (ok:init) (let ([win (ok:create-window #f ok:black 600 10 400 300)]) ; titre de la fenêtre (ok:title! win "graph") ; fonction de rappel lorqu'une touche ; est relâchée (ok:release-callback! win (lambda (key) (ok:stop))) ; fonction de rappel pour l'affichage (ok:refresh-callback! win (lambda () (proc win))) ; la fenêtre est rendue visible (ok:show device ok:show:normal)) ; boucle des messages (ok:exec)))La fonction a comme paramètre une fonction
procdestinée à dessiner le graphe dans la fenêtre lorsqu'on l'invoque avec un device. Elle crée une fenêtre et positionne la fonction de rafraîchissement et la fonction appelée lorsque qu'une touche est relâchée sur la fenêtre. Cette dernière fonction de rappel invoque la fonction ok:stop ce qui a pour effet de stopper le traitement des messages, et donc de quitter le programme.La fonction rend ensuite la fenêtre visible et entre dans la boucle de traitement des messages.
graph
La fonction principale du programme est
graphdéfinie par :; fonction principale du grapheur (define (graph mode min-x max-x step . producers) ; vérification des arguments (check (number? min-x)) (check (number? max-x)) (check (number? step)) (check (< min-x max-x)) (check (not (zero? step))) (let loop (; itération sur les producteurs [producers producers] ; les courbes produites [waves '()] ; min de toutes les courbes [waves-min-y 0] ; max de toutes les courbes [waves-max-y 0]) (if (null? producers) ; crée le device avec la fonction de tracé ; boucle sur les messages du gestionnaire (create-device (lambda (device) (plot mode device min-x max-x waves-min-y waves-max-y waves))) ; produit et collectionne les courbes ; ; calcule aussi le max et le min des y (let* (; vecteur des valeurs [values (get-values min-x max-x step (car producers))] ; minimum et maximum [min-max-y (min-max values)] [min-y (car min-max-y)] [max-y (cdr min-max-y)]) (loop (cdr producers) (cons values waves) (if (null? waves) min-y (min waves-min-y min-y)) (if (null? waves) max-y (max waves-max-y max-y)))))))La fonction est organisée autour d'une itération sur les producteurs. Après avoir vérifié la nature des arguments, la fonction entre dans une boucle sur les producteurs en collectionnant la valeur minimale et la valeur maximale de
yainsi que les vecteurs de points produits par les producteurs. Lorsqu'il n'y a plus de producteur, un device d'affichage est créé, avec une fonction anonyme pour l'affichage. Cette fonction fait simplement appel àplotpour afficher les courbes. La création du device entre dans la boucle de gestion des messages ; on ne sort que lorsque la fenêtre est détruite ou que l'on relâche une touche sur la fenêtre.Le fait intéressant à remarquer dans cette fonction est la construction avantageuse de la fonction d'affichage qui 'capture' les variables dont elle a besoin. L'écriture Scheme semble naturelle est compréhensible, alors que très peu de langages de programmation permettent d'écrire ce genre de choses. Nous sommes bien en présence d'un langage avancé !
La concision du code facilite la mise au point des programmes et leur maintenance, ce qui est directement lié à la rentabilité.
Il nous reste maintenant la fonction de dessin, qui seule va permettre d'afficher les courbes.
plot
La fonction de tracé permet d'afficher les courbes dans le device. Pour le moment, deux modes de tracé sont définis : le mode point à point et le mode ligne. La fonction est définie par :
; fonction de tracé (define (plot mode device min-x max-x min-y max-y list-of-values) (let* (; marge autour de la zone d'affichage [marge 10] ; nombre de point [n (vector-length (car list-of-values))] ; dimensions de la fenêtre ; vecteur #(x y largeur hauteur) [rect (ok:rectangle device)] ; largeur de la fenêtre [dev-w (- (vector-ref rect 2) marge marge)] ; hauteur de la fenêtre [dev-h (- (vector-ref rect 3) marge marge)] ; ratio pour x et pour y [%x (/ dev-w n)] [%y (/ dev-h (- min-y max-y))] ; position des axes x et y [xpos (+ marge (number->integer (/ (* dev-w min-x) (- min-x max-x))))] [ypos (+ marge (number->integer (/ (* dev-h max-y) (- max-y min-y))))] ; convertisseur (x,y) en coordonnées écran [conv (lambda (x y) (cons (number->integer (+ marge (* (- x min-x) %x))) (number->integer (* y %y))))]) ; couleur des axes et réinit de l'origine (ok:foreground! device ok:white) (ok:origin! device 0 0) ; axe des y (ok:draw-arrow device xpos (+ dev-h marge) 0 (- dev-h) 5 4 0) (ok:draw-text device (+ xpos marge) (+ marge 5) "y") ; déplacement de l'origine (ok:origin! device 0 ypos) ; axe des x (ok:draw-arrow device marge 0 dev-w 0 5 4 0) (ok:draw-text device (- dev-w marge) 0 "x") ; pour toutes courbes... (for-each (lambda (values) ; obtenir et régler la couleur (ok:foreground! device (get-plot-color)) (case mode [(dot) ; mode point à point (do ([i 0 (+ i 1)]) ((>= i n)) (let ([dot (conv i (vector-ref values i))]) (ok:draw-point device (car dot) (cdr dot))))] [(line) ; mode ligne (let loop ([i 0] [last #f]) (if (< i n) (let ([dot (conv i (vector-ref values i))]) (if last (ok:draw-line device (car last) (cdr last) (- (car dot) (car last)) (- (cdr dot) (cdr last)))) (loop (+ i 1) dot))))])) list-of-values)))La fonction obtient tout d'abord les dimensions de la fenêtre puis calcule les ratios permettant de convertir les valeurs (x,y) en coordonnées écran. La position des axes x et y est calculée, puis une fonction de conversion
convest définie ; cette fonction prend un couple x et y et retourne une paire de coordonnées écran. Elle trace ensuite les axes x et y, puis en fonction du mode de tracé désiré, elle effectue le tracé de toutes les courbes. Les courbes sont tracées en mode point à point, pour chaque valeur de point, un point est dessiné à la position écran correspondante. En mode ligne, le point précédent est nécessaire pour tracer une ligne entre lui et le point actuel.
![]()
![]()
![]()
![]()
![]()
Next: Améliorations possibles Up: Grapheur Previous: Introduction   Contents   Index © 1993 to 2001 Erian Concept