domenica 13 novembre 2011

[VB.NET] Dati Controllo Chart

Descrizione :
Esempio pratico di associazione dati e loro rappresentazione con il controllo Chart.

+ Articolo :

Anzitutto, dal momento che di "controlli chart" in giro ce ne sono davvero tanti, voglio precisare che l'argomento in esame riguarda esclusivamente il controllo incluso in Visual Studio ( in questo caso : 2010 ), e per l'esattezza quello in Forms :
System.Windows.Forms.DataVisualization.Charting.Chart().

Ho creato questo articolo per un duplice scopo :
1. Mostrare una tecnica valida per collegare un Chart con le sue varie Serie, ad una unica fonte dati, come un DataTable.
2. Illustrare 4 diversi modi per evidenziare ciascun dato, una volta che è rappresentato graficamente nel Chart.

Oltre a questo, si noterà come diversi controlli, come le ComboBox per la scelta di ciascun Punto-Dati, vengano automaticamente messe in relazione all'atto della loro associazione con una fonte dati comune.

Per replicare l'articolo occorre una semplice Form "FormMain", con i seguenti controlli essenziali :
Chart --> Chart1
ComboBox --> cmb_x
ComboBox --> cmb_serie2
ComboBox --> cmb_serie3
Le Label sono a scopo descrittivo.

Un'immagine rende bene l'idea della struttura desiderata e del funzionamento ottenuto :


--> Codice completo FormMain :

Public Class FormMain

    Private fontLab As New Font(FontFamily.GenericSansSerif, 14, FontStyle.Regular)
    Private DT As New DataTable("TabellaDati")
    Private percorsoBmp As String = Application.StartupPath & "\"
    Private nomeBmp As String = "arrow.bmp"

    Private Function RandomInteger(ByVal min As Integer, ByVal max As Integer, ByVal seed As Integer) As Integer
        Return (New Random(seed)).Next(min, max)
    End Function

    Private Sub FormMain_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        'Formattazione DataTable Tabella Dati per contenere due serie
        'Asse X comune alle Serie Dati
        DT.Columns.Add("X", GetType(System.Int32))
        DT.PrimaryKey = {DT.Columns("X")}
        'Asse Y Serie1
        DT.Columns.Add("YSerie1", GetType(System.Single))
        'Asse Y Serie2
        DT.Columns.Add("YSerie2", GetType(System.Int32))
        'Asse Y Serie3
        DT.Columns.Add("YSerie3", GetType(System.Int32))
        'Asse Y Serie4
        DT.Columns.Add("YSerie4", GetType(System.Int32))

        'Creazione Dati ( Pseudo-Random )
        For i As Integer = 1 To 20
            DT.Rows.Add({i, RandomInteger(10, 25, i ^ 2) * 1.2, _
                            RandomInteger(25, 50, i ^ 3), _
                            RandomInteger(50, 75, i ^ 4), _
                            RandomInteger(75, 100, i ^ 5)})
        Next
        'Creazione Serie
        Chart1.Series.Clear()
        Chart1.DataSource = DT
        'Serie1
        Dim serie1 As New DataVisualization.Charting.Series("Serie1")
        With serie1
            .ChartType = DataVisualization.Charting.SeriesChartType.Column
            .Color = Color.Goldenrod
            .BorderWidth = 1
            .BorderColor = Color.Black

            .XValueMember = DT.Columns("X").ToString
            .YValueMembers = DT.Columns("YSerie1").ToString
        End With
        Chart1.Series.Add(serie1)
        serie1.ToolTip = serie1.Name & " [ #VALX{F0} | #VALY{F2} ]"

        'Serie2
        Dim serie2 As New DataVisualization.Charting.Series("Serie2")
        With serie2
            .ChartType = DataVisualization.Charting.SeriesChartType.Line
            .Color = Color.Red
            .BorderWidth = 2
            .MarkerSize = 12
            .MarkerBorderColor = Color.Black
            .MarkerBorderWidth = 2
            .MarkerStyle = DataVisualization.Charting.MarkerStyle.Diamond

            .XValueMember = DT.Columns("X").ToString
            .YValueMembers = DT.Columns("YSerie2").ToString
        End With
        Chart1.Series.Add(serie2)
        'Serie3
        Dim serie3 As New DataVisualization.Charting.Series("Serie3")
        With serie3
            .ChartType = DataVisualization.Charting.SeriesChartType.Line
            .Color = Color.SlateBlue
            .BorderWidth = 2
            .MarkerSize = 10
            .MarkerBorderColor = Color.Blue
            .MarkerBorderWidth = 2
            .MarkerStyle = DataVisualization.Charting.MarkerStyle.Circle

            .XValueMember = DT.Columns("X").ToString
            .YValueMembers = DT.Columns("YSerie3").ToString
        End With
        Chart1.Series.Add(serie3)
        'Serie4
        Dim serie4 As New DataVisualization.Charting.Series("Serie4")
        With serie4
            .ChartType = DataVisualization.Charting.SeriesChartType.Line
            .Color = Color.Green
            .BorderWidth = 2
            .MarkerSize = 8
            .MarkerBorderColor = Color.DarkGreen
            .MarkerBorderWidth = 2
            .MarkerStyle = DataVisualization.Charting.MarkerStyle.Square

            .XValueMember = DT.Columns("X").ToString
            .YValueMembers = DT.Columns("YSerie4").ToString
        End With
        Chart1.Series.Add(serie4)

        With cmb_x
            .DropDownStyle = ComboBoxStyle.DropDownList
            .DataSource = DT
            .ValueMember = DT.Columns("X").ToString
            .DisplayMember = DT.Columns("X").ToString
        End With

        With cmb_serie2
            .DropDownStyle = ComboBoxStyle.DropDownList
            .DataSource = DT
            .ValueMember = DT.Columns("X").ToString
            .DisplayMember = DT.Columns("YSerie2").ToString
        End With

        With cmb_serie3
            .DropDownStyle = ComboBoxStyle.DropDownList
            .DataSource = DT
            .ValueMember = DT.Columns("X").ToString
            .DisplayMember = DT.Columns("YSerie3").ToString
        End With

    End Sub

    Private Sub cmb_serie2_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmb_serie2.SelectedIndexChanged

        With Chart1.Series("Serie2")
            If .Points.Count = 0 Then Exit Sub
            For i As Integer = 0 To .Points.Count - 1
                .Points(i).Label = String.Empty
                .Points(i).MarkerStyle = DataVisualization.Charting.MarkerStyle.Diamond
            Next
        End With

        With Chart1.Series("Serie2").Points(cmb_serie2.SelectedIndex)
            .Font = fontLab
            .Label = "Serie2 - " & "X = " & cmb_serie2.SelectedValue & " | Y = " & cmb_serie2.Text
            .LabelBackColor = Color.Gold
            .LabelBorderColor = Color.Black
            .LabelBorderWidth = 1
            .LabelForeColor = Color.Black
            .MarkerStyle = DataVisualization.Charting.MarkerStyle.Star10
        End With

    End Sub

    Private Sub cmb_serie3_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmb_serie3.SelectedIndexChanged

        With Chart1.Series("Serie3")
            If .Points.Count = 0 Then Exit Sub
            For i As Integer = 0 To .Points.Count - 1
                .Points(i).MarkerImage = String.Empty
            Next
        End With

        With Chart1.Series("Serie3").Points(cmb_serie3.SelectedIndex)
            .MarkerImage = percorsoBmp & nomeBmp
            .MarkerImageTransparentColor = Color.White
        End With

    End Sub

    Private Sub Chart1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Chart1.MouseMove

        Dim HTR As DataVisualization.Charting.HitTestResult = Chart1.HitTest(e.X, e.Y)
        'Controllo sul fatto che sia un DataPoint valido
        If Not HTR.ChartElementType = DataVisualization.Charting.ChartElementType.DataPoint Then Exit Sub
        'Controllo sul fatto che sia la Serie desiderata ( Serie4 )
        If Not HTR.Series.Name = "Serie4" Then Exit Sub

        With Chart1.Series("Serie4")
            For i As Integer = 0 To .Points.Count - 1
                .Points(i).Label = String.Empty
                .Points(i).MarkerStyle = DataVisualization.Charting.MarkerStyle.Square
            Next
        End With
        With Chart1.Series("Serie4").Points(HTR.PointIndex)
            .Font = fontLab
            .Label = "Serie4 - " & "X = " & .XValue & " | Y = " & .YValues(0)
            .LabelBackColor = Color.YellowGreen
            .LabelBorderColor = Color.Black
            .LabelBorderWidth = 1
            .LabelForeColor = Color.Black
            .MarkerStyle = DataVisualization.Charting.MarkerStyle.Star10
        End With

    End Sub

End Class

Note :

--> Il Chart, le Serie e anche le ComboBox puntano al medesimo DataTable, chiaramente ciascuno al suo o suoi campi di pertinenza.

--> Serie1 : evidenzia il dato al passaggio del Mouse come semplice ToolTip.

--> Serie2 : evidenzia il dato ( scelto in cmb_serie2 ) con una Label. A mio avviso la scelta più completa e flessibile.

--> Serie3 : evidenzia il dato ( scelto in cmb_serie3 ) con una bitmap caricata da disco. In questo caso sarà necessario predisporre un'immagine che abbia al suo centro il Marker, e uno o più indicatori esterni. La figura seguente mostra come ho creato la mia "arrow.bmp" in 3 semplici passi, aiutandomi semplicemente con le Forme di Word 2007 :


--> Serie4 : evidenzia il dato al passaggio del Mouse con una Label. Comportamento molto simile al ToolTip della Serie1, ma molto più flessibile nella gestione.

--> Come già accennato, una selezione su ciascuna delle 3 ComboBox si propaga alle altre in automatico e senza che sia necessario estendere gli Handles nelle routine di evento, o scrivere alcun codice aggiuntivo, perchè ciò accade solo grazie alle associazioni fatte in precedenza con i Campi del DataTable.

+ Fine Articolo.

Un Click su "Mi Piace" è il modo migliore per ringraziare l'autore di questo articolo.



20 commenti:

Giovanni Lacchei ha detto...

Scusami Ma Come si fa a Cambiare il colore di testo e di fondo del Toltip della chart

MarcoGG ha detto...

Ciao. La risposta veloce alla tua domanda non c'è, nel senso che, se fai caso, la Proprietà ToolTip di una Serie è una String, e NON una Classe System.Windows.Forms.ToolTip.
Perciò un sistema alternativo potrebbe essere quello di aprire una Label ( vedi Serie2 in questo stesso articolo ), ma questa volta al passaggio del mouse e non più al cambio del valore nella ComboBox associata. Come verificare il punto Mouse e la serie su cui si trova lo sai già : è lo stesso sistema che abbiamo visto in quel thread sullo spostamento XY di una Serie con il Mouse... ;)

Giovanni Lacchei ha detto...

Grazie Penso Di aver capito ora

Giovanni Lacchei ha detto...

Ho provato a fare in questo modo ma forse sbaglio con le coordinate del punto, ecco il Codice

Private Sub Chart1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Chart1.MouseMove

Dim PuntoSelezionato As DataVisualization.Charting.HitTestResult = Chart1.HitTest(e.X, e.Y)

If PuntoSelezionato.ChartElementType = DataVisualization.Charting.ChartElementType.DataPoint Then
With Chart1.Series(0).Points(e.X)
.Font = DefaultFont
.LabelBackColor = Color.Black
.LabelBorderColor = Color.Gold
.LabelBorderWidth = 3
.LabelForeColor = Color.White
.MarkerStyle = DataVisualization.Charting.MarkerStyle.Star10
End With

Chart1.Series(0).Points(e.X).Label = ControlChars.Lf & Chart1.Series(0).Name & ControlChars.Lf & "[ Metri #VALX{f0} ]" &
ControlChars.Lf & "#VALY{f2} Km. All'Ora " & ControlChars.NewLine & ControlChars.Lf
Else

Chart1.Series(0).Points(e.Y).Label = String.Empty
End If
end sub

Giovanni Lacchei ha detto...

come Faccio per fare comparire la label nel punto selezionato con lo scorrere del Mause ?

MarcoGG ha detto...

Nessun problema. Provvederò io appena possibile a modificare l'articolo, includendo anche questa quarta possibilità.

Giovanni Lacchei ha detto...

sto aspetto articolo nuovo come da te promesso

MarcoGG ha detto...

Articolo aggiornato.

Giovanni Lacchei ha detto...

Grazie Marco ora ho capito come fare, e nell'eventualità che ci siano più series visualizzate ho inserito questo codice, cicalando tutte le series e azzerandole

Private Sub Chart1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Chart1.MouseMove

Dim HTR As DataVisualization.Charting.HitTestResult = Chart1.HitTest(e.X, e.Y)
'Controllo sul fatto che sia un DataPoint valido
If Not HTR.ChartElementType = DataVisualization.Charting.ChartElementType.DataPoint Then Exit Sub
'Controllo sul fatto che sia la Serie desiderata ( Serie4 )
'If Not HTR.Series.Name = "Serie4" Then Exit Sub
Dim p As Integer
For Each Serie As Series In Chart1.Series
For x As Integer = 0 To Chart1.Series(p).Points.Count - 1
Chart1.Series(p).Points(x).Label = String.Empty
Next
p += 1
Next

With Chart1.Series("Serie4").Points(HTR.PointIndex)
.Font = fontLab
.Label = "Serie4 - " & "X = " & .XValue & " | Y = " & .YValues(0)
.LabelBackColor = Color.YellowGreen
.LabelBorderColor = Color.Black
.LabelBorderWidth = 1
.LabelForeColor = Color.Black
.MarkerStyle = DataVisualization.Charting.MarkerStyle.Star10
End With

End Sub

Anonimo ha detto...

[MARCOGG] TEST MESSAGGIO VISITATORE ANONIMO

Anonimo ha detto...

ciao Marco ho preso spunto da questo tuo articolo ed ho un problema con due serie di tipo "Column" che hanno come ascisse (X) una stringa. Io vorrei che quando con il puntatore del mouse sono sopra una di queste barre (che rappresentano il valore x,y ) vorrei recuperare il valore della x e metterlo ad esempio in una textbox! Però non ne sto venendo fuori! Io credo che debba usare il .XValueMember ma mi ritorna il nome e non il valore, come posso fare se vuoi ti invio l'esempio che ho fatto.

grazie
Angelo

MarcoGG ha detto...

Ciao Angelo. La risposta più semplice al tuo quesito sta nel ricavare anzitutto l'indice dell'elemento selezionato, quindi l'HitTestResult. Il che significa :
HTR.PointIndex
La Proprietà del DataPoint che restituisce la stringa su Asse X è : .AxisLabel.
Perciò, dato un certo HitTestResult ( HTR ) :
OggettoChart.Series("nomeSerie").Points(HTR.PointIndex).AxisLabel

Anonimo ha detto...

Grande Marco funonzia alla grande!!!

grazie!
Angelo

Anonimo ha detto...

Ciao Marco senti, volevo chiederti una cosa, basandomi sul tuo codice ho provato ad implementare un grafico che piglia i dati da 2 DataTable con le seguenti istruzioni:

chartStatistiche.DataSource = DT
chartStatistiche.DataSource = DT2

caricate precedentemente, solo che il grafico mostra solo i dati dell'ultima DT2 (in questo caso!) dove sbaglio esiste un modo per visualizzare il grafico con due sorgenti dati differenti?
In pratica devo mettere sullo stesso grafico delle "entrate/uscite" mensili, quindi nelle ascisse ho i giorni e nelle ordinate le entrate/uscite, le due DT mi servono perchè il grafico deve confrontare i mesi di 2 anni diversi con giorni diversi, puoi aiutarmi? ciao Angelo

MarcoGG ha detto...

La risposta richiederebbe un certo esame dei dati in tuo possesso. Comunque, se in Asse X hai valori diversi, ossia scale diverse, non puoi, e non potresti nemmeno inserire le due serie come campi di uno stesso DataTable. La soluzione che sembra più semplice è di lavorare sui dati originari, ossia escludere l'anno e il mese ( informazioni che vengono fornite a parte nel programma... ). Quindi avere solo UN DT con 31 Giorni e 3 Campi : "Campo X", "Serie Y1", "Serie Y2"...

Anonimo ha detto...

Allora in pratica ho 2 Tabelle uguali di spese mensili con i seguenti campi: IDConto,Data,Importo con le date di anni differenti ad es:
tab1:
anno 2011 mese di agosto
tab2:
anno 2012 mese di agosto
e dovrei rappresentare graficamente le entrate/uscite che ho avuto nello stesso mese ma in anni diversi. Forse il problema si ha perchè le spese non sono fatti nello stesso giorno ed inoltre ci possono essere nello stesso giorno diversi movimenti tipo entrate ed uscite:
es:

Tab: spese_2011
IDConto, Data, Importo
01 01/08/2011 +50€
01 01/08/2011 -23€
01 03/08/2011 -17€
02 07/08/2011 -9€
02 11/08/2011 +17€

Tab: spese_2012
IDConto, Data, Importo
01 04/08/2012 -150€
01 04/08/2012 +23€
01 09/08/2012 -70€
02 21/08/2011 -10€
02 23/08/2011 +17€

secondo te cosa mi conviene fare?
grazie
Aneglo

MarcoGG ha detto...

L'Asse dei valori X deve essere lo stesso per tutte le Serie presenti nel DT. A questo problema puoi ovviare facendo "confluire" i due elenchi in uno solo. Chiaramente devi pensare a come riformattare le date in modo che siano ordinate e distinte. Un'idea potrebbe essere, come già dicevo, quella di eliminare l'informazione sull'Anno, da fornirsi al di fuori del Chart. Quello che interessa sono i giorni, mentre già si sa che gli anni sono 2011 e 2012. E anche il mese è lo stesso, quindi queste informazioni potrebbero andare come nome stesso delle Serie. Serie1 : 2011/08 - Serie2 : 2012/08. In pratica Si prende l'UNIONE di tutte le date presenti nei 2 elenchi di partenza, numerando progressivamente quelle che replicano lo stesso giorno. Questi valori "Giorno-NumeroProgressivo" saranno i punti X del Chart, e così non ci saranno "buchi" :

01-001
01-002
03-001
04-001
04-002
07-001
09-001
11-001
21-001
23-001

Ogni Serie avrà il suo valore nel punto X di sua pertinenza. Se non c'è valore, la Serie in quel punto avrà uno zero.

Anonimo ha detto...

Ok! ci provo però ho un dubbio siccome io vorrei rappresentare gli importi positivi con una serie e quelli negativi con un altra per ogni mese, cioè io avrei due serie per ogni mese da confrontare magari blu e rosse quelle del primo mese e blu-scuro e arancio quelle del secondo mese, supponendo di riuscire a avere i dati come tu mi consigli, mi si presenta questo problema, o sbaglio?

grazie

MarcoGG ha detto...

Non puoi avere tutto in due sole Serie. In questo caso ne avrai 4 :
Serie1 --> 2011/08 Importi+
Serie2 --> 2011/08 Importi-
Serie3 --> 2012/08 Importi+
Serie4 --> 2012/08 Importi-

In ogni caso devi uniformare i punti temporali dell'Asse X come ti ho spiegato, altrimenti non potrai sovrapporre le Serie in modo da confrontarle visivamente nella stessa area...

Anonimo ha detto...

Grazie Marco, con questo post mi hai risolto un problemone...
Maurizio

Posta un commento

 
Design by Free WordPress Themes Modificato da MarcoGG