Riportiamo di seguito un programma utile qualora si volesse tracciare l'istogramma tridimensionale di una data quantità di riferimento, di cui si suppone di conoscere i valori assunti in diversi istanti di tempo. Si è ipotizzato, in questo caso, di voler rappresentare 20 valori numerici su di un istogramma, posti su due file, in modo dunque che i primi 10 appartenessero ad un primo anno di riferimento, i restanti a quello successivo.
Dopo averi inserito i valori numerici che si vogliono rappresentare, si procede con l'inizializzazione delle variabili necessarie per il disegno del grafico (tra cui numero dei dati inseriti, massimo tra gli stessi, numero di file su cui si posizioneranno le barre, nonché altri riferimenti spaziali e l'oggetto grafico nel quale comparirà l'istogramma):
Public Class Form1
Private WithEvents PictureBox1 As New PictureBox
Dim datiGrafico() As Long = {23, 98, 69, 78, 80, 59, 64, 58, 69, 74, 69, 135, 89, 67, 59, 23, 89, 44, 12, 96}
Dim numeroValori As Integer = 20
Dim maxValore As Long = CalcolaMassimo(datiGrafico)
Dim nFile As Integer = 2
Dim bordo As Integer = 10
Dim bordoDX As Integer = 30
Dim larghBarra As Integer = 6
Dim profRiferimento As Integer = CType(nFile * larghBarra / 2, Integer)
Dim x1, y1 As Single
Dim x2, y2 As Single
Dim scaleX As Single
Dim scaleY As Single
Dim gr As Graphics
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)
Dim larghezza As Integer = CType(x2 * (Me.Size.Height - 32) / y2, Integer)
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
Viene creata una funzione che restituisca il massimo valore ottenuto sul grafico:
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
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)
p = CType(p / 2, Integer)
Vengono determinate le facce, rispettivamente, laterali e superiori di ciascuna 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)}
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)}
A questo punto, è possibile disegnare le barre stesse:
gr.FillRectangle(coloreF, posX, posY, l, h)
gr.FillPolygon(coloreL, FacciaVerticale)
gr.FillPolygon(coloreS, FacciaSuperiore)
End Sub
Private Sub PictureBox1_Paint1(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) _
Handles PictureBox1.Paint
gr = e.Graphics
Vengono definiti i colori di ciascuna faccia dei parallelepipedi, in modo che quelle relative al primo anno di riferimento (dunque, alla prima fila) siano gialle, per il secondo anno arancio e, di seguito, blu. Nell'esempio riportato, comunque, saranno visualizzate solamente due file, che appariranno pertanto, rispettivamente, gialle ed arancio.
Dim bluF As Color = Color.FromArgb(178, 0, 0, 255)
Dim bluS As Color = Color.FromArgb(204, 50, 150, 200)
Dim bluL As Color = Color.FromArgb(204, 50, 100, 150)
Dim brushBluF As New SolidBrush(bluF)
Dim brushBluS As New SolidBrush(bluS)
Dim brushBluL As New SolidBrush(bluL)
Dim rossoF As Color = Color.FromArgb(217, 247, 100, 30)
Dim rossoS As Color = Color.FromArgb(204, 209, 111, 81)
Dim rossoL As Color = Color.FromArgb(204, 170, 97, 74)
Dim brushRossoF As New SolidBrush(rossoF)
Dim brushRossoS As New SolidBrush(rossoS)
Dim brushRossoL As New SolidBrush(rossoL)
Dim gialloF As Color = Color.FromArgb(217, 255, 255, 50)
Dim gialloS As Color = Color.FromArgb(204, 230, 230, 50)
Dim gialloL As Color = Color.FromArgb(204, 177, 177, 75)
Dim brushGialloF As New SolidBrush(gialloF)
Dim brushGialloS As New SolidBrush(gialloS)
Dim brushGialloL As New SolidBrush(gialloL)
Si impone inoltre che, laddove il valore massimo ottenuto sia maggiore di zero, questo venga approssimato per eccesso al multiplo di dieci più vicino, mentre, in caso contrario, si esca immediatamente.
If (maxValore Mod 10) > 0 Then
maxValore = (maxValore \ 10) * 10 + 10
ElseIf maxValore = 0 Then
Exit Sub
End If
Oltre a definire numerosi aspetti puramente tecnici, si impone che, relativamente alla seconda fila di parallelepipedi, l'origine sia traslata nel punto (10, 10):
Dim penAssi As New Pen(Color.DarkGray, -1)
Dim labelFont As New Font("Arial", 3, FontStyle.Regular)
Dim ColoreTesto As New SolidBrush(Color.Blue)
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)
Dim larghezza As Integer = CType(x2 * (Me.Size.Height - 40) / y2, Integer)
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
gr.ScaleTransform(scaleX, -scaleY)
gr.TranslateTransform(x1, -y2)
dopo aver pulito la casella, viene disegnata la griglia:
gr.Clear(Color.White)
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)
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
Vengono tracciati, di seguito, i parallelepipedi che costituiranno l'istogramma:
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
gr.ResetTransform()
gr.ScaleTransform(scaleX, scaleY)
gr.TranslateTransform(x1, y1)
E, a questo punto, vengono inseriti la legenda ed il testo che comparirà su ciascuno dei due assi:
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
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
For i As Integer = 1 To nFile
gr.DrawString("200" & i.ToString, labelFont, ColoreTesto, x2 - 18, 6 + 7 * i)
Next
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
Laddove l'utente cambi ridimensioni manualmente la finestra, è opportuno che anche il grafico creato subisca la stessa azioni, mantenendo inalterata la scala:
Private Sub Form1_Resize(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Resize
Me.Refresh()
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs)
End Sub
End Class
L'istogramma riportato sarà: