Frequentando qualche forum su VB.Net ho più volte letto domande su come realizzare dei grafici per rappresentare dei dati in modo chiaro e gradevole.
Senza nulla togliere all'uso dei report, mi sono divertito a programmare questa piccola applicazione che permette di disegnare degli istogrammi 3D, dimostrando che con un pò di pazienza , e un pò di tempo si riescono ad ottenere dei buoni risultati anche per conto proprio.
Il programma non effettua alcun collegamneto a database. Per disegnare gli istogrammi utilizza una matrice di dati che per necessità ho inserito fra le istruzioni. Naturalmente, tale array dovrà essere sostituito con i dati personali che ognuno inserirà nella propria applicazione attraverso i metodi che meglio si adattano alle proprie esigenze.
Avviamo un nuovo progetto e digitiamo le seguenti istruzioni:
Public Class Form1 Private WithEvents PictureBox1 As New PictureBox 'dati da visualizzare Dim datiGrafico() As Long = {23, 98, 69, 78, 80, 59, 64, 58, 69, 74, 69, 135, 89, 67, 59, 23, 89, 44, 12, 96} 'numero di dati inseriti Dim numeroValori As Integer = 20 'recupera il max valore inserito nella matrice datiGrafico() Dim maxValore As Long = CalcolaMassimo(datiGrafico) 'numero di file di grafici Dim nFile As Integer = 2 'spessore bordi Alto, Basso e SX Dim bordo As Integer = 10 'spessore bordo Dx: serve più grande per disegnare la legenda Dim bordoDX As Integer = 30 'dimensione della larghezza della singola barra Dim larghBarra As Integer = 6 'dimensione della profondità del sistema di riferimento Dim profRiferimento As Integer = CType(nFile * larghBarra / 2, Integer) 'coordinate angolo alto sx Dim x1, y1 As Single 'coordinate angolo basso dx Dim x2, y2 As Single 'definizione scala lungo l'asse x Dim scaleX As Single 'definizione scala lungo l'asse y Dim scaleY As Single 'oggetto Graphics Dim gr As Graphics
Nell'evento Load della form, impostiamo i valori delle coordinate del punto in alto a dx della PictureBox1; inoltre la If..Then consente di determinare le misure della PictureBox1 in modo da adattarla alla dimensioni della form, senza sconvolgere le proporzioni del disegno.
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load x2 = CType(bordo + numeroValori / nFile * 10 + profRiferimento + bordoDX, Single) y2 = CType(bordo + maxValore + profRiferimento + bordo, Single) 'definisce la larghezza della PictureBox in relazione alle dimensioni della Form Dim larghezza As Integer = CType(x2 * (Me.Size.Height - 32) / y2, Integer) 'stabilisce le dimesnioni della PictureBox in modo da avere la stessa scala su entrambi gli assi 'e una delle due dimensioni uguale alla corrispondente lunghezza della Form. If larghezza <= Me.Size.Width Then PictureBox1.Width = larghezza PictureBox1.Height = Me.Size.Height - 32 Else PictureBox1.Width = Me.Size.Width PictureBox1.Height = CType(y2 * Me.Size.Width / x2, Integer) End If PictureBox1.Location = New Point(0, 0) PictureBox1.Anchor = CType((((System.Windows.Forms.AnchorStyles.Top Or _ System.Windows.Forms.AnchorStyles.Bottom) Or _ System.Windows.Forms.AnchorStyles.Left) Or _ System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles) Me.Controls.Add(PictureBox1) End Sub
Scriviamo una funzione che recupera il valore massimo tra quelli inseriti nei dati da analizzare.
Questo valore ci consentirà di calcolare la corretta scala per la visualizzazione del grafico.
'funzione che recupera il massimo valore inserito nella matrice datiGrafico()
Private Function CalcolaMassimo(ByVal valore() As Long) As Long
Dim risultato As Long = 0
For Each numero As Long In valore
If numero > risultato Then risultato = numero
Next
Return risultato
End Function
Realizziamo il disegno della barra da rappresentare.
La sub richiede i seguenti parametri:
ColoreF= colore faccia frontale
ColoreL= colore faccia laterale
ColoreS= colore faccia superiore
posX, posY= coordinate del punto in basso a Sx della barra
l,p,h=larghezza, profondità e altezza della barra.
Private Sub DrawBox(ByVal coloreF As SolidBrush, ByVal coloreL As SolidBrush, ByVal coloreS As SolidBrush, _ ByVal posX As Integer, ByVal posY As Integer, ByVal l As Integer, ByVal p As Integer, _ ByVal h As Integer) 'spostamento sia orizzontale che verticale per disegnare in prospettiva la profondità della barra p = CType(p / 2, Integer) 'struttura point che definisce la faccia laterale della barra Dim FacciaVerticale() As Point = {New Point(posX + l, posY + h), New Point(posX + l + p, posY + h + p), _ New Point(posX + l + p, posY + p), New Point(posX + l, posY)} 'struttura point che definisce la faccia superiore della barra Dim FacciaSuperiore() As Point = {New Point(posX + l, posY + h), New Point(posX + l + p, posY + h + p), _ New Point(posX + p, posY + h + p), New Point(posX, posY + h)} 'disegna la barra gr.FillRectangle(coloreF, posX, posY, l, h) gr.FillPolygon(coloreL, FacciaVerticale) gr.FillPolygon(coloreS, FacciaSuperiore) End Sub
Adesso usiamo l'evento Paint della PictureBox per rappresentare il grafico.
Private Sub PictureBox1_Paint1(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) _ Handles PictureBox1.Paint gr = e.Graphics 'definizione dei colori per la barra Dim bluF As Color = Color.FromArgb(178, 0, 0, 255) ' Blu Frontale Dim bluS As Color = Color.FromArgb(204, 50, 150, 200) 'Blu Superiore Dim bluL As Color = Color.FromArgb(204, 50, 100, 150) 'Blu Laterale 'definizione dei pennelli associati ai colori Dim brushBluF As New SolidBrush(bluF) Dim brushBluS As New SolidBrush(bluS) Dim brushBluL As New SolidBrush(bluL) 'definizione dei colori per la barra Dim rossoF As Color = Color.FromArgb(217, 247, 100, 30) ' Rosso Frontale Dim rossoS As Color = Color.FromArgb(204, 209, 111, 81) 'Rosso Superiore Dim rossoL As Color = Color.FromArgb(204, 170, 97, 74) 'Rosso Laterale 'definizione dei pennelli associati ai colori Dim brushRossoF As New SolidBrush(rossoF) Dim brushRossoS As New SolidBrush(rossoS) Dim brushRossoL As New SolidBrush(rossoL) 'definizione dei colori per la barra Dim gialloF As Color = Color.FromArgb(217, 255, 255, 50) ' Giallo Frontale Dim gialloS As Color = Color.FromArgb(204, 230, 230, 50) 'Giallo Superiore Dim gialloL As Color = Color.FromArgb(204, 177, 177, 75) 'Giallo Laterale 'definizione dei pennelli associati ai colori Dim brushGialloF As New SolidBrush(gialloF) Dim brushGialloS As New SolidBrush(gialloS) Dim brushGialloL As New SolidBrush(gialloL) 'se il valore massimo >0 allora lo si approssima per eccesso al multiplo di 10 più vicino, se è zero si esce dalla sub If (maxValore Mod 10) > 0 Then maxValore = (maxValore \ 10) * 10 + 10 ElseIf maxValore = 0 Then Exit Sub End If 'penna per gli assi Dim penAssi As New Pen(Color.DarkGray, -1) 'font e colore testo per i valori sugli assi Dim labelFont As New Font("Arial", 3, FontStyle.Regular) Dim ColoreTesto As New SolidBrush(Color.Blue) 'spostamnto dell'origine nel punto 10,10 x1 = 0 y1 = 0 x2 = CType(bordo + numeroValori / nFile * 10 + profRiferimento + bordoDX, Single) y2 = bordo + maxValore + profRiferimento + bordo scaleX = Me.PictureBox1.Width / (x2 - x1) scaleY = Me.PictureBox1.Height / (y2 - y1) 'definisce la larghezza della PictureBox in relazione alle dimensioni della Form Dim larghezza As Integer = CType(x2 * (Me.Size.Height - 40) / y2, Integer) 'stabilisce le dimesnioni della PictureBox in modo da avere la stessa scala su entrambi gli assi 'e una delle due dimensioni uguale alla misura corrispondente della Form. If larghezza <= Me.Size.Width Then PictureBox1.Width = larghezza PictureBox1.Height = Me.Size.Height - 40 Else PictureBox1.Width = Me.Size.Width PictureBox1.Height = CType(y2 * Me.Size.Width / x2, Integer) End If 'trasformazione per disegnare il grafico nella finestra corrente gr.ScaleTransform(scaleX, -scaleY) 'traslazione dell'origine del sistema di riferimento gr.TranslateTransform(x1, -y2) 'pulizia dell'area del grafico con colore bianco gr.Clear(Color.White) 'colore delle parete del sistema di riferimento Dim colorePareti As Color = Color.FromArgb(218, 221, 223) Dim brushPareti As New SolidBrush(colorePareti) Dim SistemaDiRiferimento() As Point = { _ New Point(CInt(x1 + bordo), CInt(y1 + bordo)), _ New Point(CInt(x2 - profRiferimento - bordoDX), CInt(y1 + bordo)), _ New Point(CInt(x2 - bordoDX), CInt(y1 + bordo + profRiferimento)), _ New Point(CInt(x2 - bordoDX), CInt(y2 - bordo)), _ New Point(CInt(x1 + bordo + profRiferimento), CInt(y2 - bordo)), _ New Point(CInt(x1 + bordo), CInt(y2 - bordo - profRiferimento))} gr.FillPolygon(brushPareti, SistemaDiRiferimento) gr.DrawPolygon(Pens.DarkGray, SistemaDiRiferimento) 'disegna la griglia For xScan As Single = 0 To CSng((numeroValori / nFile * 10) - 10) Step 10 gr.DrawLine(penAssi, xScan + bordo + profRiferimento, y1 + bordo + profRiferimento, _ xScan + bordo + profRiferimento, maxValore + bordo + profRiferimento) gr.DrawLine(penAssi, xScan + bordo + profRiferimento, y1 + bordo + profRiferimento, _ xScan + bordo, y1 + bordo) Next xScan For yScan As Single = 0 To maxValore - 10 Step 10 Dim lunghezza As Integer = CInt(numeroValori / nFile * 10) gr.DrawLine(penAssi, x1 + bordo + profRiferimento, yScan + bordo + profRiferimento, _ lunghezza + bordo + profRiferimento, yScan + bordo + profRiferimento) gr.DrawLine(penAssi, x1 + bordo, yScan + bordo, x1 + bordo + profRiferimento, _ yScan + bordo + profRiferimento) Next yScan 'disegna le barre For n As Integer = nFile - 1 To 0 Step -1 For xScan As Single = 0 To CSng((numeroValori / nFile) - 1) Dim elemento As Integer = CInt(xScan + n * (numeroValori / nFile)) If (n Mod 3) = 0 Then DrawBox(brushGialloF, brushGialloL, brushGialloS, CInt(xScan * 10 + 12 + 3 * n), 10 + 3 * n, _ larghBarra, larghBarra, CInt(datiGrafico(elemento))) ElseIf (n Mod 2) = 0 Then DrawBox(brushBluF, brushBluL, brushBluS, CInt(xScan * 10 + 12 + 3 * n), 10 + 3 * n, _ larghBarra, larghBarra, CInt(datiGrafico(elemento))) Else DrawBox(brushRossoF, brushRossoL, brushRossoS, CInt(xScan * 10 + 12 + 3 * n), 10 + 3 * n, _ larghBarra, larghBarra, CInt(datiGrafico(elemento))) End If Next xScan Next 'annullamento delle trasformazioni per la scrittura del testo, altrimenti verrebbe visualizzato capovolto gr.ResetTransform() gr.ScaleTransform(scaleX, scaleY) gr.TranslateTransform(x1, y1) 'disegna la cornice della legenda gr.DrawRectangle(penAssi, x2 - 25, 10, 20, 3 + nFile * 7) For i As Integer = nFile - 1 To 0 Step -1 If (i Mod 3) = 0 Then gr.FillRectangle(brushGialloF, x2 - 23, 13 + i * 7, 5, 4) ElseIf (i Mod 2) = 0 Then gr.FillRectangle(brushBluF, x2 - 23, 13 + i * 7, 5, 4) Else gr.FillRectangle(brushRossoF, x2 - 23, 13 + i * 7, 5, 4) End If Next 'disegno del testo For xScan As Single = 0 To CSng(numeroValori / nFile * 10) Step 10 gr.DrawString(xScan.ToString, labelFont, ColoreTesto, _ CSng(bordo + xScan + 1.7 - 2 * xScan.ToString.Length), y2 - bordo + 3) Next xScan For yScan As Single = 0 To maxValore Step 10 gr.DrawString(yScan.ToString, labelFont, ColoreTesto, _ bordo - 2 * yScan.ToString.Length - 3, y2 - bordo - yScan) Next yScan 'disegna il testo della legenda. 'queste istruzioni sono state inserite come esempio, ma devono essere sostituite con una sub appropriata For i As Integer = 1 To nFile gr.DrawString("200" & i.ToString, labelFont, ColoreTesto, x2 - 18, 6 + 7 * i) Next 'rilascio delle risorse labelFont.Dispose() ColoreTesto.Dispose() penAssi.Dispose() brushBluF.Dispose() brushBluL.Dispose() brushBluS.Dispose() brushRossoF.Dispose() brushRossoS.Dispose() brushRossoL.Dispose() brushGialloF.Dispose() brushGialloS.Dispose() brushGialloL.Dispose() gr = Nothing End Sub 'aggiornamento della finestra quando questa viene ridimensionata Private Sub Form1_Resize(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Resize Me.Refresh() End Sub End Class
Grafico ottenuto con numeroValori=15 e nFile=1
Grafico ottenuto con numeroValori=20 e nFile=2
Grafico ottenuto con numeroValori=12 e nFile=3
Sebbene sia possibile aumentere il numero delle file, non è consigliabile andare oltre le tre, per ovvi motivi di visibilità.