Appunti di Programmazione

Creative Commons License

Grafici 3D senza Report

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

Nome Variabili

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.

Parametri

    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

Grafici 1 Fila

Grafico ottenuto con numeroValori=15 e nFile=1

Grafici 2 File

Grafico ottenuto con numeroValori=20 e nFile=2

Grafici 3 File

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à.

Download sorgente "Grafici_3D.zip" ( 70KB )