P4A 3 Framework: La nuova release 3.2.0, realizzare una maschera con una riga di codice e tabella editabile

screenshot della grid di P4A 3.2.0
screenshot della grid di P4A 3.2.0
La nuova versione del framework PHP P4A si presenta nel “versioning” con un salto di minor di 2 punti (da 3.0.3 a 3.2.0). Questo indica che le novità sono molte e significative. Forse Fabrizio e Andrea erano in vena di regali natalizi…! Strano, però che non ci sia stato ancora un annuncio ufficiale, io l’ho scoperto quasi per caso. Avevo letto in questo post di Andrea che il lancio era imminente, ma non mi sarei aspettato di trovare nell’area download di sourceforge il pacchetto P4A 3.2.0 già disponibile. Siccome le nuove caratteristiche mi stuzzicavano, l’ho subito provato.

Fra le novità più rilevanti c’è il cambio di tipo di licenza che passa dalla AGPL3 alla LGPL3. Non sono un esperto in materia, e non so dirvi molto. Da quanto ho capito si tratta di una differenza formale che regolamenta l’utilizzo e interazioni fra Librerie, Applicazioni e Lavori combinati.

Passando alle note tecniche, a parte una serie di patch e fix che risolvono alcuni bug, le novità più interessanti sono due. Una è quella del nuovo widget P4A_grid. Si tratta in pratica di una tabella editabile, molto utile per modificare singoli campi senza dover per forza utilizzare il fieldset e la toolbar.
La P4A_grid eredita dalla P4A_table tutti i metodi e le proprietà, in aggiunta presenta i seguenti:

  • void autoSave ()
  • void getRows ($num_page, $rows)
  • void preChange ([$params = null])
  • void saveData ($obj, $params)

Non ho avuto tempo di provare tutto, ma ecco un piccolo test con una tabella di prova per un’ipotetica gestione fatture:

1
2
3
4
5
6
7
  // grid
  $this->build("p4a_grid", "grid")
  ->setWidth(600)
  ->setSource($this->source)
  ->setVisibleCols(array("numero", "descrizione", "cliente"))
  ->showNavigationBar()
  ->autosave();

Il risultato è quello della immagine in alto, chiaramente il campo “lookup” non è editabile, ma il risultato è comunque fantastico. Se poi ci fosse la possibilità di riconoscere automaticamente i campi boolean e renderizzarli con una ckeckbox, e le “lookup” in select sarebbe ancora meglio! (dal post di Andrea sembra che la cosa sia già in cantiere…) Se avesse a disposizione anche un metodo per aggiungere record e cancellarli, sarebbe davvero perfetta, benché con un poco di codice e l’utilizzo dei metodi newRow() e deleteRow() questo dovrebbe essere già possibile.

Dulcis in fundo una piccola chicca, che trovo fantastica!
La possibilità di generare un’intera maschera con tutte le funzionalità per interagire con una tabella, scrivendo due sole righe di codice:

1
2
3
4
5
6
7
8
class test_clienti extends P4A_Base_Mask
{
	public function __construct()
	{
		parent::__construct();
		$this->constructSimpleEdit("clienti");
	}
}

Ma forse due righe sono troppe… ecco come fare scrivendone una sola!

1
2
3
4
5
6
7
8
class test_clienti extends P4A_Simple_Edit_Mask
{
 
	public function __construct()
	{
	 parent::__construct("clienti");
	}	
}

Questo il risultato in uno screenshot:

screenshot della P4A_Simple_Edit_Mask
screenshot della P4A_Simple_Edit_Mask

Nel primo caso utilizzo un nuovo helper (oggetto comodissimo particolarmente per il riutilizzo del codice) che si chiama: P4A_Mask_constructSimpleEdit() e nel secondo una classe che deriva da P4A_Base_Mask

Ci sono numerose altre novità come le migliorie nel comportamento dei CSS, la maggiore aderenza agli standard XHTML ed altri ancora che sono elencati nel file CHANGELOG.

Conclusioni

In attesa della presentazione ufficiale, mi sono permesso di elencare i due aspetti che più mi sono piaciuti! Lunga vita a P4A!

Riferimenti ed approfondimenti:

PHP: una classe per generare un calendario stampabile in PDF

screenshot del calendarioQuesta classe PHP genera un calendario gregoriano in PDF, disponendo ogni mese in una pagina con l’elenco dei giorni feriali e delle festività. E’ possibile scegliere l’anno e cambiare il logo che compare in testa alla pagina.

Si avvicina l’anno nuovo e quasi tutti siamo alla ricerca di calendari. Sebbene molto spesso ci vengono regalati a scopo pubblicitario, una buona parte di questi calendari sono poco utili. Infatti, troppo spesso si tende a privilegiare l’aspetto estetico, a corredo grandi immagini, belle per carità, ma poi i giorni sono scritti piccoli che ci vuole la lente per leggerli. Oppure sono pieni zeppi di informazioni tanto inutili quanto ingombranti, di nomi di santi assolutamente improponibili per gli onomastici dei viventi di questa era, ed ancora, così minimalisti che non sono segnate nemmeno le festività nazionali.

Per questi motivi, ed anche per imparare qualcosa di più sulle funzioni di data e ora in PHP, ho pensato di scrivere una classe in grado di generare il mio calendario da appendere in cucina!

Il codice è abbastanza versatile: genera un’array multidimensionale con i mesi in lettere, i giorni in numero, i giorni della settimana, e un flag che indica se il giorno corrisponde ad una festività oppure no. La classe estende le lbrerie PDF R&OS, in questo modo è sufficiente istanziare una sola classe per generare anche il PDF.

Ecco il codice:

1
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<?php 
require_once("class.ezpdf.php");
 
class calendarPDF extends Cezpdf {
 
    var $calArray = array();
    var $yearNum = NULL;
    var $logo = NULL;
    var $easterDay = array();
 
    function buildCalendar($yearYouWant,$logoPath){
      $this->yearNum = empty($yearYouWant) ? intval(date("Y")) : $yearYouWant;
      $this->logo = $logoPath;
      $e = strtotime("$yearYouWant-03-21 +".
                            easter_days($yearYouWant)." days");
      $this->easterDay = array("month"=>intval(date("n",$e)), 
                               "day"=>intval(date("j",$e)));
 
      for ($i=1;$i<=12;$i++) {
        $this->buildMonths($i);        
      }
    }
 
   function buildMonths ($monthNum)
   {
    $giorniSettimana = array("Domenica","Lunedì","Martedì","Mercoledì",
                             "Giovedì","Venerdì","Sabato");
    $mesiAnno = array("GENNAIO","FEBBRAIO","MARZO","APRILE","MAGGIO",
                      "GIUGNO","LUGLIO","AGOSTO","SETTEMBRE","OTTOBRE",
                      "NOVEMBRE","DICEMBRE");                              
    $firstDay = mktime(0,0,0,$monthNum,1,$this->yearNum);
    $daysPerMonth = intval(date("t",$firstDay));
    $monthName = $mesiAnno[$monthNum-1];
    for ($i = 1; $i<=$daysPerMonth; $i++)
    {
      $actualDay = mktime(0,0,0,$monthNum,$i,$this->yearNum);
      $numDay = intval(date("w",$actualDay));
 
      if ($numDay == 0 ) {
        $f = true;
      }
      elseif ($this->isHoliday($monthNum,date("j",$actualDay))) {
          $f = true;
        }
      elseif (($monthNum == $this->easterDay['month']) && 
                (intval(date("j",$actualDay)) == $this->easterDay['day']+1)) {
          $f = true;
      }
      else {
          $f = false;
      }
 
      $this->calArray[$monthName][] = array("day"=>date("d",$actualDay), 
                                      "weekday"=>iconv('UTF-8','CP1252',
                                      $giorniSettimana[$numDay]),
                                      "F"=>$f);                          
    }   
   }
 
   function isHoliday ($monthNumber,$dayNumber)
   {
    $monthNumber = intval($monthNumber);
    $dayNumber = intval($dayNumber);
    $holiday = array(1=>array(1,6), 
                     4=>array(25),
                     5=>array(1),
                     6=>array(2),
                     8=>array(15),
                     11=>array(1),
                     12=>array(8,25,26)                                      
                     );
     $holidayKeys = array_keys($holiday);
     if (in_array($monthNumber,$holidayKeys)){
       if (in_array($dayNumber,$holiday[$monthNumber])) {
        return true;
       }
     }
     return false;
   }
}
?>

Per ottenere il testo in italiano, ho scelto di non utilizzare le funzioni setlocale() e strftime() perché, come viene spiegato in questo post, a seconda del sistema operativo impiegato, cambia la stringa per l’impostazione della lingua, passata come secondo parametro a setlocale(). Per ovviare a questo fastidioso comportamento, ho preferito costruire due array, uno per i nomi dei mesi ed uno per i nomi dei giorni della settimana.
Per la determinazione delle festività ho impostato in un array le date nazionali italiane, ovvero

  • 1 e 6 gennaio
  • 25 aprile
  • 1 maggio
  • 2 giugno
  • 15 agosto
  • 1 novembre
  • 8, 25 e 26 dicembre

Per ottenere, invece, la festività del lunedì dopo Pasqua, è necessario calcolare la data della Pasqua che varia in dipendenza del ciclo lunare e del tipo di calendario (noi utilizziamo il gregoriano, gli ortodossi il giuliano). Chi ha voglia di saperne di più sull’algoritmo di calcolo può fare un’approfondimento qui, chi invece è pigro (come me) può ricorrere alle funzioni PHP easter_date() o easter_days() che restituiscono rispettivamente la data della Paqua in UTS (valida solo fra il 1970 e il 2037) e il numero di giorni dopo il 21 marzo dell’anno in corso.
Poiché easter_date() risulta essere affetta da un bug riportato qui, ho preferito usare easter_days() che riporta sempre date corrette.

Ecco infine il codice per generare il calendario in PDF:

1
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
40
<?php
    require_once("calendario_ita.php");
 
    $cal = new calendarPDF('a4','portrait');
    $cal->buildCalendar(2009,"./logo.png");
    $cal->selectFont('./fonts/Helvetica.afm');
    $tmp = array(
      'b'=>'Helvetica-Bold.afm'
      ,'i'=>'Helvetica-Oblique.afm'
      ,'bi'=>'Helvetica-BoldOblique.afm'
      ,'ib'=>'Helvetica-BoldOblique.afm'
      ,'bb'=>'Helvetica-Bold.afm'
    );
    $cal->setFontFamily("Helvetica.afm",$tmp);
    $cal->ezSetMargins(30,20,60,30);
 
    foreach ($cal->calArray as $calMonth => $dati) {
      $cal->ezImage($cal->logo,0,490,"none","left");
      $cal->setColor(0,0,1,0);
      $monthString = "<b>".$calMonth." ".$cal->yearNum."</b>";
      $cal->ezSetY(700);
      $cal->ezText($monthString,18);
      $cal->ezSetDY(-10);
      $cal->setColor(0,0,0);
      foreach ($dati as $singleDay) {
        $toPrint = $singleDay['day']."  ".$singleDay['weekday'];
        if ($singleDay['F']) {
            $toPrint = "<b>".$toPrint."</b>";
            $cal->setColor(1,0,0,0);
        }   
        $y = $cal->ezText($toPrint,14);
        $cal->setColor(0,0,0,0);
        $cal->setLineStyle(1);
        $cal->line(155,$y,540,$y);
        $y = $cal->ezSetDY(-4);
      }
      $cal->ezNewPage();
    }
    $cal->stream();
?>

Se volete solo stampare il calendario cliccate qui, se invece siete interessati anche al codice, in questo pacchetto è disponibile la classe (compresa la libreria R&OS) e il codice per gli esempi.

Conclusioni

Semplice, se nessuno mi regala un calendario… me lo stampo da me!!

Riferimenti ed approfondimenti:

PHP: triangolazioni di Delaunay in 2D

Costruzione dei triangoli di Delaunay (fonte: Wikipedia)Questa particolare tecnica di Geometria computazionale, che prende il nome dal russo Boris Nikolaevich Delone, francesizzato in Delaunay, consente di definire una griglia di triangoli in una superficie in 2D o in 3D, dato un insieme di punti P.
La griglia viene costruita in modo che, per ogni circonferenza circoscritta ad un triangolo, nessun punto di P (oltre a quelli che formano il triangolo stesso) giace all’interno della circonferenza. Questa condizione viene anche detta di cironferenza libera.

La libreria di funzioni, che propongo è la traduzione “letterale” in PHP delle sole funzioni per superfici in 2D, scritte in codice Visual Basic 6 da Franco Languasco, disponibili qui. Il pacchetto di Languasco è davvero molto ben fatto e contempla anche le funzioni per le triangolazioni in 3D, nonchè la visualizzazione grafica del risultato e la possibilità di ruotare i 3 assi. Il codice iniziale risale in realtà alle librerie GEOMPACK3 di Barry Joe originariamente scritte nel 1991 in linguaggio Fortran.

E’ bene precisare che avendo fatto una traduzione “letterale”, il mio codice PHP non risulta molto elegante, e soprattutto non sfrutta appieno le caratteristiche del linguaggio, sebbene già dai primi test, sembra che le prestazioni siano soddisfacenti!

Esistono diversi algoritmi per le triangolazioni di Delaunay, i più importanti sono:

  • Incrementale con complessità O(N2)
  • Dividi et impera con complessità O(NlogN)
  • Convex Hull con complessità O(NlogN)

L’algoritmo incrementale è quello utilizzato in questa libreria, sebbene non sia chiaramente il più efficiente, va più che bene per un insieme non troppo grande di punti.

Funzionamento:

L’algoritmo incrementale descritto da Dani Lischinski in questo documento, inizia con la costruzione di un primo triangolo, largo a sufficienza per contenere tutti i punti dati. I punti vengono aggiunti uno alla volta verificando che la condizione di circonferenza libera venga rispettata.
La prima operazione è quella di connettere i vertici del triangolo su cui giace il nuovo punto con il punto stesso.

algoritmo incrementale passo uno

Si vengono così a formare 3 nuovi triangoli. A questo punto i triangoli vengono verificati per la condizione di circonferenza libera.

algoritmo incrementale passo due

Se è rispettata, si procede con un nuovo punto altrimenti viene eseguita un’operazione che prende il nome di edge flip.

algoritmo incrementale passo tre

Questa operazione si basa sul concetto che la triangolazione di Delaunay massimizza l’angolo minimo.
Poiché in due triangoli che hanno un lato in comune, la somma degli angoli opposti a tale lato è sempre minore di 180°, sarà sufficiente scambiare le diagonali del quadrilatero formato dai due triangoli come mostrato nella figura sotto.

algoritmo incrementale passo tre

Nel codice della libreria di funzioni, ho volutamente lasciato tutti i commenti originali e ho commentato le istruzioni originali Visual Basic senza cancellarle, per poter controllare meglio eventuali errori di traduzione.
L’intera libreria è visibile qui. Questo, invece, è il codice del file di esempio:

1
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<?php 
require_once("delaunay2D.php");
/*
================== PROVA =======================================================
001:   (4.111   0.668)
002:   (-4.209   -3.961)
003:   (-9.720   5.214)
004:   (4.181   -9.093)
005:   (7.252   5.810)
006:   (9.239   7.429)
007:   (8.991   -2.720)
Risultato:
001:   (2   1   3)
002:   (1   2   4)
003:   (1   4   7)
004:   (3   1   5)
005:   (5   1   7)
006:   (3   5   6)
007:   (6   5   7)
*/
//dati
 
  $labels = array("001","002","003","004","005","006","007");
  $XI =  array(4.111,-4.209,-9.720,4.181,7.252,9.239,8.991);
  $YI =  array(0.668,-3.961,5.214,-9.093,5.810,7.429,-2.720); 
 
if (count($XI) <> count($YI)){
  die("le coordinate delle X non sono in numero uguale alle Y");
}
else {
  $NP = count($XI); //N° di punti.  
}
 
echo "Numero punti: ".$NP."<br />";
 
$NT = 0; // N° di triangoli.
 
$IERR = 0;
// Inizializza vettori
$Vcl = array();
$Ind = array();
$TIL = array();
$TNBR = array();
 
    echo ('&lt;pre&gt;');
    for ($I = 1; $I <= $NP; $I++) {
        // Organizza le coordinate dei vertici come
        // richiesto dalle routines di delaunay2D:
        $Vcl[1][$I] = $XI[$I-1]; //Vettore con indice iniziale 1
        $Vcl[2][$I] = $YI[$I-1]; //Vettore con indice iniziale 1
        $Ind[$I] = $I;  // Triangolazione di tutti i vertici contenuti in VCL().
        echo ("Punto $I ".$labels[$I-1]." (".
              $Vcl[1][$I].",".$Vcl[2][$I].")<br />");
    }
    echo ('&lt;/pre&gt;');
 
    // Costruisce le triangolazioni
    $res = DTRIW2($NP, $Vcl, $Ind, $NT, $TIL, $TNBR, $IERR);
 
    echo ("<h2>N. triangoli: ".$NT."</h2>");
 
    if ($IERR == 0) {
      echo ('&lt;pre&gt;');
          for ($I = 1; $I <= $NT; $I++) {
              $Triang[1][$I] = $TIL[1][$I];
              $Triang[2][$I] = $TIL[2][$I];
              $Triang[3][$I] = $TIL[3][$I];
              echo ("Triangolo $I: (".$Triang[1][$I].",".$Triang[2][$I].
                    ",".$Triang[3][$I].")<br />");            
          }
      echo ('&lt;/pre&gt;');        
    }
    else {
      echo "Errore n.: $IERR  - ".zCodiciErrore($IERR)."<br />";
    }
?>

L’array $XI contiene le ascisse dei punti, mentre $YI le ordinate. Tutti i vettori devono avere indice iniziale 1. Le coordinate dei punti vengono passate mediante la matrice $Vcl e il vettore degli indici $Ind alla funzione DTRIW2(). L’output viene fornito passando per riferimento la matrice $TIL così come il vettore per i codici di errore $IERR.

Ho preparato anche questa piccola demo dove è anche possibile visualizzare graficamente i punti su una gif, e al passaggio del mouse, i triangoli che compongono la griglia. Per questo effetto ho utilizzato una map area che viene generata dallo script php e JQuery maphilight uno script che consente di evidenziare le forme geometriche definite nelle map area.

Download

In questo pacchetto è disponibile la libreria e il codice per gli esempi

Ringraziamenti

Ringrazio Franco Languasco per la sua ottima versione Visual Basic 6 senza la quale sarebbe stata dura! Federico Villa, Project Director di Target Tobrukper avermi dato lo spunto a scrivere questo post, e Alessandro Scoscia per le sue dritte geniali!

Conclusioni

Non ho trovato molto sulla generazione di griglie di superfici 2D utilizzando il metodo di Delaunay in PHP, questo potrebbe essere un inizio. Spero, in futuro di migliorare il “porting”, al momento piuttosto grezzo, di questa libreria. Infine, una raccomandazione: non ho potuto testare in modo esaustivo il codice, quindi prima di utilizzarla è bene fare un po’ di prove. Fatemi sapere, in caso di errori!

Riferimenti ed approfondimenti:

PHP: il punto e’ dentro o fuori dal poligono convesso?

Pentagono regolareQuesta classe PHP determina se un certo punto di coordinate P(x,y) si trova all’interno di un poligono convesso oppure no. Inoltre calcola la lunghezza di ciascun lato, il perimetro, l’area, e l’ampiezza di ciascun angolo.Può essere applicata a qualsiasi poligono convesso di cui si abbiano le coordinate cartesiane dei vertici in 2D. Le coordinate dei vertici verranno fornite in stretta successione, non importa se in senso orario o antiorario, in un array multidimensionale. E’ stato scelto di considerare solo poligoni convessi, per semplificare il calcolo degli angoli interni che risulteranno tutti < di 180°. La condizione che il poligono fornito sia convesso viene controllata verificando che il segno di ogni lato con il vertice successivo non cambi. Con lo stesso metodo viene verificato se un punto dato si trova all’interno o all’esterno del poligono. Infine, per semplificare, ho considerato che se il punto giace su uno dei lati, viene considerato interno.
La classe è compatibile con PHP 4.

Funzionamento:

Dati due punti A(x0,y0); e B(x1,y1), possiamo definire l’equazione canonica della retta passante per i due punti come:
x (y0 − y1) - y (x0 − x1) + x0 y1 - x1 y0 = 0
Il polinomio che è rappresentato dal primo membro dell’equazione, sarà = 0 nel caso di un punto sulla retta, produrrà valori positivi se si trova su un lato e negativi se si trova sul lato opposto.
E’ dunque chiaro che per verificare se un punto si trova all’interno del poligono è sufficiente confrontare il segno che assume l’equazione, sostituendo ad x e y le coordinate del punto, rispetto ad ogni lato. Se il segno cambia, si trova all’esterno.
Poiché ho imposto che il poligono sia convesso, ho potuto ridurre l’algoritmo confrontando il risultato del segno fra ogni lato e il punto dato, solo con il segno del primo lato e il vertice successivo (nei poligoni convessi ogni lato “gira” dalla stessa parte).

Il calcolo della misura di ciascun lato viene fatto usando la formula della distanza fra due punti:

Formula della distanza fra due punti

Si intendono p(px,py) e q(qx,qy) due vertici del poligono.

Il calcolo della misura di ciascun angolo viene fatto usando la formula del coseno:

a2 = b2 + c2 - 2 bc cos α

In cui α è l’angolo compreso fra b e c.

L’area viene calcolata usando la formula Shoelace:

Formula Shoelace

Considerando che: (xn + 1; yn + 1) = (x1; y1).

Ecco il codice:

1
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
<?php
class ConvexPolygon2D
{
  var $EPSILON = 0.000001;
  var $points = array();
  var $polySides = array();
  var $polyAngles = array();
  var $nVertex = 0;
  var $polySign = 0;
 
  function ConvexPolygon2D($points)
  {
    $this->points = $points;
    $this->nVertex = count($points);
    $this->polySign = $this->sign($points[0],$points[1],$points[2]);
    if (!$this->isConvexHull()) die ("This polygon is not convex!"); 
    $this->sides();
    $this->angles();
  }  
 
  function sign ($a, $b, $p)
  {
    $c = $p[0] * ($a[1] - $b[1]) - $p[1] * ($a[0] - $b[0]) + 
         $a[0] * $b[1] - $a[1] *$b[0];
    if (abs($c)< $EPSILON)    
    {
      return 0;
    }
    elseif ($c > 0)
    {
      return 1;
    }
    else
    {
      return -1;
    }
  }
 
  function isConvexHull()
  {
    for ($i=0; $i<$this->nVertex-1; $i++)
    {
      $lastVertex = $i+2;
      if ($lastVertex>=$this->nVertex)
         $lastVertex = 0;
      $psign = $this->polySign * 
               $this->sign($this->points[$i],
               $this->points[$i+1],
               $this->points[$lastVertex]);
      if($psign==0) die ("Two sides on the same line!"); 
      if ($psign<0)
      {
        return false;
      }
 
    }
    return true;
  }
 
  function isInConvexHull ($p)
  {
    for ($i=0; $i<=$this->nVertex-1; $i++)
    {
      $lastVertex = $i+1;
      if ($lastVertex>=$this->nVertex)
         $lastVertex = 0;
         $psign = $this->polySign * 
                $this->sign($this->points[$i],$this->points[$lastVertex],$p);
 
      if ($psign<0)
      {
        return false;
      }
 
    }
    return true;
  }  
 
  function RadToDeg($radians)
  {
    return $radians * 180 / M_PI;
  }
 
  function vertexDist($a,$b)
  {
    return sqrt(pow(($b[0]-$a[0]),2)+pow(($b[1]-$a[1]),2));
  }
 
  function perimeter()
  {
    return array_sum($this->polySides);
  }
 
 
  function sides()
  {
    for ($i=0; $i<$this->nVertex; $i++)    
    {
      $lastVertex = $i+1;
      if ($lastVertex>=$this->nVertex)
         $lastVertex = 0;
      $this->polySides[] = $this->vertexDist($this->points[$i],
                           $this->points[$lastVertex]);
    }
    return true; 
  }
 
  function angles()
  {
    for ($i=0; $i<$this->nVertex; $i++)    
    {
      $this->polyAngles[] = $this->angle($i);
    }
    return true;    
  }
 
  function angle($vertex)
  {
    //Teorema del coseno: a² = b² + c² - 2bc cosα
    $a = empty($vertex) ? $this->vertexDist($this->points[$this->nVertex-1],
        $this->points[1]) : 
        ($vertex==$this->nVertex-1 ? $this->vertexDist($this->points[$vertex-1],
        $this->points[0]) : $this->vertexDist($this->points[$vertex-1],
        $this->points[$vertex+1]));
 
    $b = $this->polySides[$vertex];
    $c = empty($vertex) ? $this->polySides[$this->nVertex-1] : 
        $this->polySides[$vertex-1];
 
    $res = (pow($b,2) + pow($c,2) - pow($a,2)) / (2 * $b * $c);
    return $this->RadToDeg(acos($res));
  }
 
  function area()
  {
    //Calcolo dell'area con la Shoelace formula.
    $dArea = 0;
    for ($i=0; $i<$this->nVertex; $i++)    
    {
      $lastVertex = $i+1;
      if ($lastVertex>=$this->nVertex)
         $lastVertex = 0;
      $dArea += ($this->points[$i][0]*$this->points[$lastVertex][1]) - 
                ($this->points[$lastVertex][0]*$this->points[$i][1]);
    }
    return abs($dArea) /2;  
  }
}
?>

Un esempio del funzionamento della classe è visibile qui. Per rendere graficamente le figure dei poligoni e dei punti ho utilizzato le librerie JavaScript VectorGraphics di Walter Zorn. E’ necessario, quindi, che Javascript sia attivo nel browser.

Nota: le librerie grafiche utilizzano <div>; di tipo canvas, e con Internet Explorer, alcune figure vengono disegnate in modo non perfetto, mentre con Mozilla non ci sono problemi

Il pacchetto completo è scaricabile qui

Riferimenti ed approfondimenti:

Ajax e PHP: una progress bar V-meter style

Progress bar V-meter styleEcco un’idea per una progress bar un po’ diversa dal solito. L’aspetto è quello dei V-meter digitali degli apparecchi audio. Solo che l’andamento della progressione non è logaritmico ma decimale e percentuale.
Può essere utilizzata come strumento di monitoraggio di operazioni lunghe eseguite sul server perché, grazie alla tecnologia Ajax, è possibile recuperare valori ad intervelli regolari da uno script PHP.

I requisiti per un corretto funzionamento sono:

  • Javascript abilitato
  • PHP con supporto GD 2.0.1 o successivi
  • FreeType library

La progress bar viene generata dinamicamente da uno script PHP che esegue l’eco di un’immagine in formato PNG. I valori passati in querystring servono per colorare le barre. La percentuale viene calcolata normalizzando il valore sul minimo e il massimo, con un’approssimazione per difetto alla decina inferiore. (p.e.: 52% accende 5 barre). Il default per il minimo e il massimo è rispettivamente 0 e 100

Ecco il codice:

1
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
40
41
//==============================================================================
// PROGRESS BAR V-METER STYLE
//==============================================================================
if (!empty($_GET['value'])){
  $value = intval($_GET['value']);
  $min = empty($_GET['min']) ? 0 : intval($_GET['min']);
  $max = empty($_GET['max']) ? 100 : intval($_GET['max']);
}
else{
  $value = 0;
  $min = 0;
  $max = 100;
}
  $nValue = floor((($value-$min) / ($max-$min)) * 100);
  header("Content-type: image/png");
  $string = $nValue." %";
  $font = 'arial.ttf';
  $width  = 110; 
  $height = $width;
  $im = @imagecreatetruecolor ($width,$height);
  $width -=5;
  $height -=5;
  $background_color = imageColorAllocate($im, 0, 0, 0);
  $color = imageColorAllocate($im, 0, 255, 0);
  imagefill($im, 0, 0, $background_color);
  for($i=0;$i&lt;10;$i++){
    $x1 = $width;
    $y1 = $height-2 - ($i*10);
    $x2 = $width-10 - ($i*10);
    $y2 = $height-10 - ($i*10);
    if ($i&lt;(floor($nValue/10))){
      imageFilledRectangle($im, $x1, $y1, $x2,  $y2, $color);
    }
    else{
      imagerectangle($im, $x1, $y1, $x2,  $y2, $color);
    }
  }
  $text_color = imagecolorallocate ($im, 255, 255, 255);
  imagettftext($im, 14, 0, 5, $height-10, $text_color, $font, $string);
  imagepng($im);
  imagedestroy($im);

Volendo una più ampia compatibilità, è possibile evitare di utilizzare la funzione imagettftext() sostituendola con imagestring(), in questo caso vengono utilizzati i font interni con codifica latin2. Però non sono altrettanto belli e le dimensioni possibili sono solo 5. Il valore più grande restituisce una dimensione di carattere piuttosto piccola… ove possibile è preferibile usare i TTF

Questo è il codice di esempio per rappresentare la progress bar in una pagina web con un po’ di Ajax per rigenerare dinamicamente l’immagine ogni 2 secondi:

1
2
3
4
5
6
7
8
9
10
11
12
 
 
 
 
 
  <script type="text/javascript" language="javascript">
    <!-- var xml = ""; function getPage() { var url = "./randgen.php"; if (window.XMLHttpRequest) { xml = new XMLHttpRequest(); } else if (window.ActiveXObject) { xml = new ActiveXObject("Microsoft.XMLHTTP"); } else { alert("Your browser lacks the needed ability to use Ajax"); return false; } xml.onreadystatechange = processPage; xml.open("GET", url, true); xml.send(""); setTimeout('getPage()', 2*1000); } function processPage() { if (xml.readyState == 4) { if (xml.status == 200) { document.getElementById("myProgBar").src="./progbar.php?value=" + xml.responseText; } else { alert("There was a problem retrieving the XML data:\n" + xml.statusText); } } } //-->  
  </script>
 
  <script type="text/javascript" language="javascript">
  getPage();
  </script>

Test della progress bar V-meter style

 

progress bar

 

1
 

Lo script randgen.php genera dei numeri casuali da 0 a 100 solo a scopo dimostrativo, nella pratica dovrà generare il valore da rappresentare nella progress bar.

randgen.php:

1
2
3
4
5
6
7
8
$min = empty($_GET['min']) ? 0 : intval($_GET['min']);
$max = empty($_GET['max']) ? 100 : intval($_GET['max']);
// headers are sent to prevent browsers from caching
header('Expires: Fri, 25 Dec 1980 00:00:00 GMT'); // time in the past
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT'); 
header('Cache-Control: no-cache, must-revalidate'); 
header('Pragma: no-cache');
echo rand($min,$max);

Il pacchetto completo è scaricabile qui

Conclusioni:

Siccome adoro i V-meter analogici, per capirci quelli che si trovavano sui grandi amplificatori HiFi degli anni ’70, stavo pensando anche a qualcosa del genere per il futuro, peccato che con la grafica ho qualche difficoltà…!

Riferimenti ed approfondimenti:

P4A3 framework: un widget conta tempo che utilizza il plugin jQuery Countdown

Widget p4a CountdownAvendo la necessità di inserire in una maschera di un progetto, un timer contatempo, ho provveduto all’integrazione nel framework P4A 3, del plugin Countdown sviluppato con jQuery da Keith Wood.
P4A 3 supporta nativamente jQuery e, come nel caso di jClock, il porting è alquanto semplificato.
Al momento ho reso disponibili solo alcuni dei metodi, mi riprometto di finire l’integrazione appena avrò un po’ più di tempo!

Che cosa fa?

Mostra un contatempo con avvio sia in count-down che in count-up, configurabile nell’aspetto e nel comportamento attraverso opportuni metodi.

Elenco dei metodi:
NomeTipoDefaultNote
setSince()integernullImposta la data e l’ora di avvio in formato UNIX timestamp
setUntil()integernullImposta la data e l’ora di stop in formato UNIX timestamp.
setDisplayFormat()stringdHMS‘Y’ anni, ‘O’ mesi, ‘W’ settimane, ‘D’ giorni, ‘H’ ore, ‘M’ minuti, ‘S’ secondi. I caratteri minuscoli impostano la visualizzazione opzionale
setCompactFormat()stringfalseImposta il formato ridotto.
setDescription()string“”Imposta la descrizione del contatempo.
setLabels()string"['Years', 'Months', 'Weeks', 'Days', 'Hours', 'Minutes', 'Seconds']"Imposta il testo per le varie parti del contatempo.
setLabelsSingle()string['Year', 'Month', 'Week', 'Day', 'Hour', 'Minute', 'Second']Imposta il testo per le varie parti del contatempo se sono al singolare.
setCompactLabels()string['y', 'm', 'w', 'd']Imposta il testo in formato compatto.
setCompactLabelsSingle()string['y', 'm', 'w', 'd']Imposta il testo in formato compatto se è al singolare.
setTimeSeparator()string:Imposta il separatore per il tempo.
setServerTime()stringnullImposta l’offset per il fuso orario. Valori di esempio: ‘+1’, ‘+2’, ‘+3’, ‘-1’, ‘-2’, ‘-3’. etc.

Ecco il codice per inserire il contatempo in una maschera di esempio:

1
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
class test_countdown extends p4a_base_mask
{
 public function __construct()
 {
   parent::__construct();
   $this-&gt;setTitle("test Countdown");
   $this-&gt;build("P4A_jCountdown", "countdown")
    -&gt;setStyleProperty("width","400px")
    -&gt;setStyleProperty("float","none")
    -&gt;setStyleProperty("margin","auto")    
    //Se vogliamo impostare una data in particolare possiamo usare il
    //seguente formato:
    //-&gt;setSince($this-&gt;date2timestamp('2008-09-26 17:59:47'))
    //oppure per avviare il contatempo con l'orario attuale:
    -&gt;setSince(time())
    //Se vogliamo visualizzare sempre solo ore, minuti e secondi:
    //-&gt;setDisplayFormat('HMS')
    -&gt;setCompactFormat(false)
    //Per impostare un offset sul fuso orario:
    //-&gt;setServerTime('-1')
    -&gt;setDescription("test countdown");
 
   $this-&gt;frame-&gt;anchorCenter($this-&gt;countdown);
  }
 
  //Questa funzione converte una data in formato date in UNIX timestamp
  public function date2timestamp($value)
  {
    $date_time = explode(' ',$value);
    $date_part = explode('-',$date_time[0]);
    $time_part = explode(':',$date_time[1]);
    $res = mktime($time_part[0],$time_part[1],$time_part[2],
           $date_part[1],$date_part[2],$date_part[0]);
    return $res;    
  }
 
}

Il pacchetto completo della maschera di esempio è scaricabile qui

Note:

Ho notato che impostando l’orario attuale attraverso l’istruzione:
...
->setSince(time())
...
può capitare che venga impostato un valore di tempo successivo a quello di caricamento nel browser della pagina, probabilmente a causa della asincronicità dovuta al “motore Ajax” di P4A 3. In questi casi, il contatore non parte subito, ma al primo nuovo reload del widget. Per evitare questo comportamento, è possibile impostare il tempo attuale decrementato di 1 o 2 secondi, p.e.:->setSince(time()-1)

Conclusioni:

L’integrazione di plugin jQuery in P4A 3 è abbastanza semplice, i passi principali sono:

  • Definire le proprietà e i metodi per impostarle nella classe che descrive il widget
  • Effettuare l’overload del metodo getAsString()
  • Importare nel metodo getAsString() le librerie ed eventuali CSS con i metodi: addJavascript() e addCSS()
  • Sempre in getAsString() scrivere l’eco del codice javascript necessario.

Riferimenti ed approfondimenti:


ag ha scritto:

Bel widget, complimenti :) Potresti aggiungerlo insieme al clock nella sezione contrib del wiki…