Disegnare una spezzata con JavaFX

 Vediamo un'applicazione interessante mista tra programmazione ad oggetti in Java e l'uso di una GUI come la recente Java FX. Carichiamo una serie di punti che descrivono una linea spezzata e la grafichiamo attraverso una finestra grafica.

 Java FX è una libreria Graphical User Interface di ultima generazione, con una sorte altalenante da parte di Oracle , ma pur sempre un ottimo toolkit con prestazioni e tecnologie recenti rispetto alle classiche e famose Swing o AWT. Vogliamo approcciarci al mondo di questa GUI con un esercizio ibrido in cui usiamo l'interfaccia non per stampare a video bottoni o campi di una form, ma per disegnare una linea spezzata fornita in input dall'utente via console. L'esercizio ha due valenze: introdurre Java FX ma anche potenziare un percorso di studio della programmazione ad oggetti in Java con un esempio abbastanza complesso. Procediamo per passi.

Creiamo in NetBeans un progetto predisposto per l'uso di JavaFX. Suggerisco sempre di usare il tool Maven con il tipo Simple JavaFX Maven Archetype. Se è la prima volta che usiamo un progetto FX con Maven, probabilmente chiederà di scaricare una manciata di di plugin da spuntare. Per questo progetto scegliamo il nome PianoCartesiano e GroupId com.iisteramo. Il resto lasciamo invariato o personalizziamo a piacere.

Prima di tutto abbiamo bisogno di capire cosa sia una spezzata e come possiamo rappresentarla con degli oggetti Java. Procediamo quindi col costruire le classi che descrivono il nostro problema per poi dargli un senso grafico. Se la spezzata è un insieme di punti e segmenti che li uniscono, dobbiamo definirci i due oggetti punti e spezzata come insieme di punti.Sul menu di esplorazione del progetto, nella sezione src, troveremo una classe App.java che sarà la nostra classe "main" eseguibile, più una classe SystemInfo in realtà eliminabile. Posizioniamoci col tasto destro del mouse slla cartellina blu PianoCartesiano e procediamo a creare le classi che ci occorrono come in figura.

 

 

 

Classe Punto

package com.iisteramo.pianocartesiano;

/**
 *
 * @author Alfredo Centinaro
 */
public class Punto {
    private int x;
    private int y;

    public Punto(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    @Override
    public String toString() {
        return "Punto{" + "x=" + x + ", y=" + y + '}';
    }
    
    /*
    * Calcola la distanza tra il punto attivo e uno passato come parametro
    * E' una semplice trasosizione del teorema di Pitagora!
    */
    public double distanza(Punto p) 
    {
        return Math.sqrt(Math.pow( (p.getX() - this.getX() ),2) + Math.pow( (p.getY() - this.getY() ),2) );
    }    
}

Classe Spezzata:

package com.iisteramo.pianocartesiano;

import java.util.Scanner;

/**
 *
 * @author Alfredo Centinaro
 * La classe gestisce un insieme di punti attraverso il vettore punti
 * L'idea è ricreare la spezzata "unendo" i punti consecutivi del vettore
 */
public class Spezzata {
    
    private Punto[] punti;
    private int npunti;

    public Spezzata() {
        this.punti = new Punto[100];
        this.npunti = 0;
    }

    public Punto[] getPunti() {
        return this.punti;
    }

    public void setPunti(Punto[] punti) {
        this.punti = punti;
    }

    public int getNpunti() {
        return npunti;
    }

    public void setNpunti(int npunti) {
        this.npunti = npunti;
    }
    
    public void inserisciPunto(Punto p)
    {
        //inserisco il punto nella prima posizione libera
        //che è data dal contatore npunti
        this.punti[this.npunti] = p;
        this.npunti++;
    }        
    
    /*
    * Calcola la lunghezza totale della spezzata, sommando i suoi segmenti
    */
    public double lunghezza() {
        double l = 0;
        for(int i = 0; i<this.npunti-1; i++) 
        {
            l += this.punti[i].distanza(this.punti[i+1]);
        }
        return l;
    }
	
    @Override
    public String toString() 
    {
        String s="";
        for(int i = 0; i<this.npunti; i++) 
        {
            s += "\n";
            s += "Punto " + i;
            s += "\n";
            s += this.punti[i].toString();
            s += "\n";
        }
        return s;
    }
	
    public Punto getPuntoXY(int x, int y) 
    {
        int i = 0; 
        while(i < this.npunti) 
        {
            if(this.punti[i].getX() == x && 
               this.punti[i].getY() == y) 
            {
                return this.punti[i];
            }
            i++;
        }
        return null;
    }
	
    public Punto getPuntoI(int i)
    {
        if(i>this.npunti) 
        {
            return null;
        }
        if(i<0) 
        {
            return null;
        }
        return this.punti[i];
    }
	
    public boolean rimuoviPuntoXY(int x, int y) 
    {
        int i = 0;
        boolean foundPunto = false;
        Punto puntoRicercato = this.getPuntoXY(x,y);

        if(puntoRicercato == null) 
        {
            return false;
        }

        while(i<this.npunti && foundPunto == false) 
        {
                if(this.punti[i] == puntoRicercato) 
                {
                    foundPunto = true;
                }
                i++;
        }
        
        for(i--; i<this.npunti-1; i++) 
        {
                this.punti[i] = this.punti[i+1];
        }
        this.punti[i+1] = null;
        this.setNpunti(this.npunti-1);
        return true;
    }    
    
    
    /*
    * Metodo helper per la creazione di una spezzata con interazione utente
    * via console
    */
    public void creaSpezzata()
    {
        Scanner sc = new Scanner(System.in);
        int scelta;
        String str;        
        String strX;
        String strY;

        do 
        {
            System.out.println("Scegli il numero dell'opzione da compiere ");            
            System.out.println("Opzioni spezzata:");
            System.out.println("1: Inserisci Punto");
            System.out.println("2: Elimina Punto");
            System.out.println("3: Stampa Spezzata");
            System.out.println("0: Esci");

            str = sc.nextLine();
            scelta = Integer.valueOf(str);

            switch(scelta) 
            {
                case 1:
                    System.out.println("Inserisci coordinata x Punto: ");
                    strX = sc.nextLine();
                    System.out.println("Inserisci coordinata y Punto: ");
                    strY = sc.nextLine();
                    this.inserisciPunto(new Punto(Integer.valueOf(strX),Integer.valueOf(strY)));
                    break;
                case 2:
                    System.out.println("Inserisci coordinata x Punto: ");
                    strX = sc.nextLine();
                    System.out.println("Inserisci coordinata y Punto: ");
                    strY = sc.nextLine();
                    System.out.print("Successo operazione: ");
                    System.out.print(this.rimuoviPuntoXY(Integer.valueOf(strX),Integer.valueOf(strY)));
                    break;
                case 3:
                    System.out.println(this.toString());
                    break;

            }
        }
        while(scelta!=0);    
    }        
}
 

Una classe di supporto per la creazione del vero e proprio disegno della spezzata raccogliendo la spezzata stessa e fornendo indietro un gruppo di linee da stampare nel pannello principale.

package com.iisteramo.pianocartesiano;

import javafx.scene.shape.Line; 
import javafx.scene.Group; 

/**
 *
 * @author Alfredo Centinaro
 */
public class DisegnaSpezzata 
{
    private Group root;
    private Spezzata spezzata;

    public DisegnaSpezzata() {
        this.root = new Group();
        this.spezzata = new Spezzata();
    }

    public Group getRoot() {
        return this.root;
    }

    public void setRoot(Group root) {
        this.root = root;
    }

    public Spezzata getSpezzata() {
        return this.spezzata;
    }

    public void setSpezzata(Spezzata spezzata) {
        this.spezzata = spezzata;
    }
    

    public boolean creaRootGroup()
    {
        //Scorro il vettore della spezzata e disegno una linea per ogni coppia
        //Aggiungo le linee al gruppo root da passare alla scena
        //Creating a line object
        
        if (this.spezzata.getNpunti() == 0)
            return false;
        
        for (int i=0; i < this.spezzata.getNpunti()-1; i++)
        {
            Line line = new Line();         

            line.setStartX(spezzata.getPuntoI(i).getX()); 
            line.setStartY(spezzata.getPuntoI(i).getY()); 
            line.setEndX(spezzata.getPuntoI(i+1).getX()); 
            line.setEndY(spezzata.getPuntoI(i+1).getY());   
            
            this.root.getChildren().add(line);  //aggiunge il segmento al gruppo Fx          
        }   
        
        return true;
    }        
}

Classe App, la eseguibile si occuperà di creare una spezzata con l'input testuale dell'utente che inserisce le coordinate dei punti della spezzata, la classe DisegnaSpezzata che crea il Gorup di stampa, per poi dichiararare la Scene, la scena col gruppo preconfezionato di segmenti della spezzata.

package com.iisteramo.pianocartesiano;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;



/**
 * JavaFX App
 */
public class App extends Application {

    @Override
    public void start(Stage stage) {

        Spezzata s = new Spezzata();
        s.creaSpezzata();
        
        DisegnaSpezzata ds = new DisegnaSpezzata();
        ds.setSpezzata(s);
        ds.creaRootGroup();
                
        var scene = new Scene(ds.getRoot(), 640, 480);        
        
        stage.setTitle("Disegno di una spezzata con JavaFX");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }

}