Descrizione :
Un mio UserControl che permette di rappresentare il grafico di funzioni Y = f(x).
+ Articolo :
Questo Controllo è in grado di ricevere in input un insieme di punti X e Y, in cui tipicamente Y è funzione di X, ma anche senza che le due variabili siano necessariamente correlate, e rappresentarli su un grafico con un classico piano cartesiano XY.
Tutto ciò di cui il Controllo ha bisogno dall'esterno è perciò un insieme di punti.
Il Controllo si occupa di :
--> disegnare gli assi X e Y e le scale dei valori.
--> gestire separatamente l'insieme dei punti del grafico contenuti da quello dei punti rappresentati sul grafico : l'insieme dei punti X e Y passati in input non viene banalmente modificato e riportato sul grafico, ma il Controllo provvede a creare un secondo insieme di punti, dedotto dal primo e adatto alla rappresentazione grafica, e ad unire i punti stessi con una curva in AntiAliasing, ad alta qualità.
Ovviamente la qualità della curva sarà sempre inversamente proporzionale alla distanza tra due punti successivi, nell'insieme dei punti passati in input.
--> permettere di definire due fattori di scala separati per X e Y :
agendo sulle ComboBox dedicate a questo scopo, è possibile scalare il grafico a piacere nelle direzioni X ed Y. La curva eventualmente già contenuta nel Controllo verrà automaticamente ridisegnata, e "deformata" adattandosi al nuovo rapporto delle misure X/Y.
--> permettere di definire due fattori di traslazione dell'origine separati per X e Y :
agendo sulle ComboBox dedicate a questo scopo, è possibile personalizzare la posizione dell'origine nel grafico. Si può traslare lungo X ed Y, per visualizzare la parte di curva che interessa, sempre al centro del Controllo...
--> Autosize : basta impostare l'Anchor della PictureBox interna "pbfx" su tutti i 4 valori ( Top, Bottom, Left, Right ) e fare lo stesso sull'istanza del ControlGrafico aggiunto alla Form di test, e il grafico si adatterà a qualsiasi dimensione e risoluzione desiderata.
ControlGrafico.vb :
- Struttura :
- Codice :
Public Class ControlGrafico Private m_penassi As New Pen(Color.Black, 2) 'Pen per disegno Assi XY Private m_pengrafico As New Pen(Color.Red, 2) 'Pen per disegno Grafico Private m_fontassi As New Font(FontFamily.GenericSansSerif, 11, FontStyle.Regular) Private m_tensionecurva As Single 'valore consigliato : 0 Private m_scalax As Integer 'Scala X : il valore esprime in pixels l'unità su Asse X Private m_scalay As Integer 'Scala Y : il valore esprime in pixels l'unità su Asse Y Private m_traslazionex As Integer Private m_traslazioney As Integer Private m_originex As Integer Private m_originey As Integer Private m_estremosn As Double = -5 'Estremo sinistro incluso : [m_estremosn <= X Private m_estremodx As Double = 5 'Estremo destro incluso : X <= m_estremodx] Private m_puntifx As New List(Of PtFx) Public Sub SetPuntiFx(ByVal fx As List(Of PtFx), ByVal tensioneCurva As Single) m_tensionecurva = tensioneCurva m_puntifx = fx pbfx.Refresh() End Sub Private Sub ControlGrafico_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load For i As Integer = 30 To 200 Step 10 cmb_scalax.Items.Add(i) cmb_scalay.Items.Add(i) Next cmb_scalax.Text = cmb_scalax.Items(cmb_scalax.Items.Count - 1) cmb_scalay.Text = cmb_scalay.Items(cmb_scalay.Items.Count - 1) For i As Integer = -10 To 10 cmb_traslax.Items.Add(i) cmb_traslay.Items.Add(i) Next cmb_traslax.Text = 0 cmb_traslay.Text = 0 End Sub Private Sub pbfx_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles pbfx.Paint If Me.DesignMode = True Then Exit Sub 'Traslazione Origine Graphics pbfx m_originex = pbfx.Width / 2 + m_traslazionex * m_scalax m_originey = pbfx.Height / 2 + m_traslazioney * m_scalay With e.Graphics .ResetTransform() .TranslateTransform(m_originex, m_originey) End With 'Disegno Assi Dim p1x As New Point(-pbfx.Width - m_originex, 0) Dim p2x As New Point(pbfx.Width - m_originex, 0) Dim p1y As New Point(0, pbfx.Height - m_originey) Dim p2y As New Point(0, -pbfx.Height - m_originey) With e.Graphics .DrawLine(m_penassi, p1x, p2x) .DrawLine(m_penassi, p1y, p2y) For i As Integer = m_scalax To (pbfx.Width - m_originex) Step m_scalax .DrawEllipse(m_penassi, i, -2, 2, 2) .DrawString(i / m_scalax, m_fontassi, Brushes.Black, _ i - .MeasureString(i, m_fontassi).Width / 2, -.MeasureString(i, m_fontassi).Height) Next For i As Integer = -m_scalax To (-pbfx.Width - m_originex) Step -m_scalax .DrawEllipse(m_penassi, i, -2, 2, 2) .DrawString(i / m_scalax, m_fontassi, Brushes.Black, _ i, -.MeasureString(i, m_fontassi).Height) Next For i As Integer = m_scalay To (pbfx.Height + m_originey) Step m_scalay .DrawEllipse(m_penassi, -2, -i, 2, 2) .DrawString(i / m_scalay, m_fontassi, Brushes.Black, 0, -i) Next For i As Integer = -m_scalay To (-pbfx.Height + m_originey) Step -m_scalay .DrawEllipse(m_penassi, -2, -i, 2, 2) .DrawString(i / m_scalay, m_fontassi, Brushes.Black, 0, -i - .MeasureString(i, m_fontassi).Height / 2) Next End With 'Disegno Curva F(x) If m_puntifx Is Nothing Then Exit Sub Dim puntiFxGraf(m_puntifx.Count - 1) As Point For i As Integer = 0 To puntiFxGraf.Length - 1 Try puntiFxGraf(i).X = m_puntifx(i).X * m_scalax puntiFxGraf(i).Y = m_puntifx(i).Y * m_scalay * -1 Catch ex As Exception End Try Next With e.Graphics .SmoothingMode = Drawing2D.SmoothingMode.AntiAlias If puntiFxGraf.Length > 0 Then .DrawCurve(m_pengrafico, puntiFxGraf, m_tensionecurva) End With End Sub Private Sub pbfx_Resize(ByVal sender As Object, ByVal e As System.EventArgs) Handles pbfx.Resize pbfx.Refresh() End Sub Private Sub cmb_scalax_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmb_scalax.SelectedIndexChanged m_scalax = cmb_scalax.SelectedItem pbfx.Refresh() End Sub Private Sub cmb_scalay_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmb_scalay.SelectedIndexChanged m_scalay = cmb_scalay.SelectedItem pbfx.Refresh() End Sub Private Sub cmb_traslax_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmb_traslax.SelectedIndexChanged m_traslazionex = cmb_traslax.SelectedItem * -1 pbfx.Refresh() End Sub Private Sub cmb_traslay_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmb_traslay.SelectedIndexChanged m_traslazioney = cmb_traslay.SelectedItem pbfx.Refresh() End Sub End Class
Ovviamente è possibile esporre molte delle caratteristiche interne, come colori delle Pen di disegno, e svariati altri aspetti grafici e non, come ad esempio i valori da caricare di default nelle ComboBox ecc... Tutte cose di facile implementazione che non mi sono soffermato a realizzare, e che lascio a chi vorrà personalizzare questo Control come meglio crede.
Per ora il succo del discorso c'è tutto.
Il Metodo essenziale è SetPuntiFx().
Ho preferito esporre la Tension della curva, utile al Metodo interno di disegno .DrawCurve().
Questo parametro permette di influire in un certo modo su come il Controllo "inventa" i punti mancanti nell'insieme della funzione, e di conseguenza su come la curva verrà disegnata.
Personalmente consiglio di tenere questo valore sempre a 0. Con TensioneCurva a 0, il Controllo unirà due punti consecutivi con la retta più breve possibile.
Si possono sperimentare valori >0, ad esempio per "ammorbidire" le spezzate, ma senza eccedere, perchè può portare ad effetti indesiderati...
Nella rappresentazione di una classica funzione matematica, stile "studio di funzione", meglio creare una serie di valori chiave molto ravvicinati, piuttosto che fare troppo affidamento sulla Tension.
PtFx.vb :
E' la Structure che contiene i punti originari di ogni Funzione.
Public Structure PtFx Public X As Double Public Y As Double Public Sub New(ByVal valueX As Double, ByVal valueY As Double) X = valueX Y = valueY End Sub End Structure
E ora finalmente, un po' di esempi e il ControlGrafico in azione.
Compilare - aggiungere un "ControlGrafico1" alla Form di test - pronti - via :
1. Una classica parabola :
Dim fx As New List(Of PtFx) Dim Y As Double For X As Double = -10 To 10 Step 0.01 Y = X ^ 2 / 3 + X - 1 fx.Add(New PtFx(X, Y)) Next ControlGrafico1.SetPuntiFx(fx, 0)
2. Spezzata con valori Random :
Dim R As New Random Dim fx As New List(Of PtFx) Dim Y As Double For X As Double = 0 To 20 Y = R.Next(0, 3) fx.Add(New PtFx(X, Y)) Next ControlGrafico1.SetPuntiFx(fx, 0)
3. La Funzione di WikiPedia :
Ovvero, la funzione al momento sulla pagina Wiki : "Studio di Funzione".
http://it.wikipedia.org/wiki/File:Studio_funzione_esempio_con_derive.jpg
Dim fx As New List(Of PtFx) Dim Y As Double For X As Double = -20 To 20 Step 0.01 Y = Math.E ^ (5 * X - 3 * X ^ 2) - X ^ 3 fx.Add(New PtFx(X, Y)) Next ControlGrafico1.SetPuntiFx(fx, 0)
+ Fine Articolo.
6 commenti:
Ciao. Non offro consulenze di questo genere. I miei Articoli Blog sono disponibili così come sono per chiunque ne voglia fare uso. Chiunque fosse interessato ad approfondire alcuni argomenti trovati in questo Blog, può farlo liberamente sulla mia Pagina FaceBook :
https://www.facebook.com/pages/MarcoGG/176216775722284
Ciao,
bellissimo ed interessantissimo esercizio. Vorrei provarlo ma.. cos'è "ControlGrafico1"?
Ciao e grazie. E' molto semplice : ControlGrafico1 è il nome del Controllo creato dalla Classe di tipo UserControl, una volta che è stato aggiunto alla Form. L'articolo spiega come crearlo.
Public Class ControlGrafico e relativo codice è quanto devi scrivere nel codice dello UserControl. I suoi componenti, come spiegato in figura, vanno aggiunti allo UserControl stesso in design :
- cmb_scalax
- cmb_scalay
sono due ComboBox che servono a modificare la Scala di visualizzazione sui due assi X e Y.
- cmb_traslax
- cmb_traslay
sono due ComboBox che servono a modificare la Traslazione del grafico lungo i due assi X e Y.
- pbfx è una PictureBox destinata al disegno del grafico.
Ciao, veramente molto interessante. Ho inserito il controllo VB in una mia solution fatta di progetti in C#. Funziona perfettamente. Una domanda... per farlo funzionare ho dovuto inizializzare m_scalax e m_scalay a 1, altrimenti andava in loop. Grazie. Giorgio
Ciao, probabilmente nel tuo progetto non hai incluso gli Eventi delle ComboBox che vanno ad aggiornare i valori di m_scalax ed m_scalay :
Private Sub cmb_scalax_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmb_scalax.SelectedIndexChanged
m_scalax = cmb_scalax.SelectedItem
pbfx.Refresh()
End Sub
Questi valori dovrebbero essere maggiori di zero al primo Load del Controllo.
Posta un commento