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.
21 commenti:
Scusami Ma Come si fa a Cambiare il colore di testo e di fondo del Toltip della chart
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... ;)
Grazie Penso Di aver capito ora
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
come Faccio per fare comparire la label nel punto selezionato con lo scorrere del Mause ?
Nessun problema. Provvederò io appena possibile a modificare l'articolo, includendo anche questa quarta possibilità.
sto aspetto articolo nuovo come da te promesso
Articolo aggiornato.
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
[MARCOGG] TEST MESSAGGIO VISITATORE ANONIMO
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
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
Grande Marco funonzia alla grande!!!
grazie!
Angelo
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
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"...
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
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.
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
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...
Grazie Marco, con questo post mi hai risolto un problemone...
Maurizio
Ciao a tutti, ho usato il controllo chart in visual studio per visualizzare delle curve.
Il mio problema è che nella stessa chart Area devo visualizzare la velocità (che arriva massimo a 10 m/s), la pressione (500 bar) e i comandi delle Valvole (10000mV).
E' possibile creare diverse scale per gli assi del controllo chart (una per la velocità da 0 a 10, una per la pressione da 0 a 500 e una per i comandi da 0 a 10000).
Faccio questa domanda, perchè se no la velocità mi risulta tutta schiacciata e illeggibile.
Vi ringrazio per l'attenzione.
Posta un commento