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.cs :
- Struttura :
- Codice :
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.cs :
E' la Structure che contiene i punti originari di ogni Funzione.
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 :
2. Spezzata con valori Random :
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
+ Fine Articolo.
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.cs :
- Struttura :
- Codice :
using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; namespace UserControlGrafici { public partial class ControlGrafico : UserControl { public ControlGrafico() { InitializeComponent(); } private Pen m_penassi = new Pen(Color.Black, 2); //Pen per disegno Assi XY private Pen m_pengrafico = new Pen(Color.Red, 2); //Pen per disegno Grafico private Font m_fontassi = new Font(FontFamily.GenericSansSerif, 11, FontStyle.Regular); private float m_tensionecurva; //valore consigliato : 0 private int m_scalax; //Scala X : il valore esprime in pixels l'unità su Asse X private int m_scalay; //Scala Y : il valore esprime in pixels l'unità su Asse Y private int m_traslazionex; private int m_traslazioney; private int m_originex; private int m_originey; private List<PtFx> m_puntifx = new List<PtFx>(); public void SetPuntiFx(List<PtFx> fx, float tensioneCurva) { m_tensionecurva = tensioneCurva; m_puntifx = fx; pbfx.Refresh(); } private void ControlGrafico_Load(object sender, EventArgs e) { for (int i = 30; i <= 200; i += 10) { cmb_scalax.Items.Add(i); cmb_scalay.Items.Add(i); } cmb_scalax.Text = cmb_scalax.Items[cmb_scalax.Items.Count - 1].ToString(); cmb_scalay.Text = cmb_scalay.Items[cmb_scalay.Items.Count - 1].ToString(); for (int i = -10; i <= 10; i++) { cmb_traslax.Items.Add(i); cmb_traslay.Items.Add(i); } cmb_traslax.Text = "0"; cmb_traslay.Text = "0"; } private void pbfx_Paint(object sender, PaintEventArgs e) { if (this.DesignMode == true) return; //Traslazione Origine Graphics pbfx m_originex = Convert.ToInt32(pbfx.Width / 2.0 + m_traslazionex * m_scalax); m_originey = Convert.ToInt32(pbfx.Height / 2.0 + m_traslazioney * m_scalay); e.Graphics.ResetTransform(); e.Graphics.TranslateTransform(m_originex, m_originey); //Disegno Assi Point p1x = new Point(-pbfx.Width - m_originex, 0); Point p2x = new Point(pbfx.Width - m_originex, 0); Point p1y = new Point(0, pbfx.Height - m_originey); Point p2y = new Point(0, -pbfx.Height - m_originey); string sxy; float x; float y; e.Graphics.DrawLine(m_penassi, p1x, p2x); e.Graphics.DrawLine(m_penassi, p1y, p2y); for (int i = m_scalax; i <= (pbfx.Width - m_originex); i += m_scalax) { e.Graphics.DrawEllipse(m_penassi, i, -2, 2, 2); sxy = (i / m_scalax).ToString(); x = i - e.Graphics.MeasureString(i.ToString(),m_fontassi).Width / 2.0f; y = -e.Graphics.MeasureString(i.ToString(), m_fontassi).Height; e.Graphics.DrawString(sxy, m_fontassi, Brushes.Black, x, y); } for (int i = -m_scalax; i >= (-pbfx.Width - m_originex); i -= m_scalax) { e.Graphics.DrawEllipse(m_penassi, i, -2, 2, 2); sxy = (i / m_scalax).ToString(); x = i; y = -e.Graphics.MeasureString(i.ToString(), m_fontassi).Height; e.Graphics.DrawString(sxy, m_fontassi, Brushes.Black, x, y); } for (int i = m_scalay; i <= (pbfx.Height + m_originey); i += m_scalay) { e.Graphics.DrawEllipse(m_penassi, -2, -i, 2, 2); sxy = (i / m_scalay).ToString(); x = 0; y = -i; e.Graphics.DrawString(sxy, m_fontassi, Brushes.Black, x, y); } for (int i = -m_scalay; i >= (-pbfx.Height + m_originey); i -= m_scalay) { e.Graphics.DrawEllipse(m_penassi, -2, -i, 2, 2); sxy = (i / m_scalay).ToString(); x = 0; y = -i - e.Graphics.MeasureString(i.ToString(), m_fontassi).Height / 2.0f; e.Graphics.DrawString(sxy, m_fontassi, Brushes.Black, x, y); } //Disegno Curva F(x) if (m_puntifx == null) return; Point[] puntiFxGraf = new Point[m_puntifx.Count]; for (int i = 0; i < puntiFxGraf.Length; i++) { try { puntiFxGraf[i].X = Convert.ToInt32(m_puntifx[i].X * m_scalax); puntiFxGraf[i].Y = Convert.ToInt32(m_puntifx[i].Y * m_scalay) * -1; } catch (Exception ex) { } } e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; if (puntiFxGraf.Length > 0) { e.Graphics.DrawCurve(m_pengrafico, puntiFxGraf, m_tensionecurva); } } private void pbfx_Resize(object sender, EventArgs e) { pbfx.Refresh(); } private void cmb_scalax_SelectedIndexChanged(object sender, EventArgs e) { m_scalax = Convert.ToInt32(cmb_scalax.SelectedItem); pbfx.Refresh(); } private void cmb_scalay_SelectedIndexChanged(object sender, EventArgs e) { m_scalay = Convert.ToInt32(cmb_scalay.SelectedItem); pbfx.Refresh(); } private void cmb_traslax_SelectedIndexChanged(object sender, EventArgs e) { m_traslazionex = Convert.ToInt32(cmb_traslax.SelectedItem) * -1; pbfx.Refresh(); } private void cmb_traslay_SelectedIndexChanged(object sender, EventArgs e) { m_traslazioney = Convert.ToInt32(cmb_traslay.SelectedItem); pbfx.Refresh(); } } }
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.cs :
E' la Structure che contiene i punti originari di ogni Funzione.
namespace UserControlGrafici { public struct PtFx { public double X; public double Y; public PtFx(double valueX, double valueY) : this() { X = valueX; Y = valueY; } } }
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 :
List<PtFx> fx = new List<PtFx>(); double Y = 0; for (double X = -10; X <= 10; X += 0.01) { Y = (Math.Pow(X, 2)) / 3 + X - 1; fx.Add(new PtFx(X, Y)); } controlGrafico1.SetPuntiFx(fx, 0);
2. Spezzata con valori Random :
Random R = new Random(); List<PtFx> fx = new List<PtFx>(); double Y = 0; for (double X = 0; X <= 20; X++) { Y = R.Next(0, 3); fx.Add(new PtFx(X, Y)); } 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
List<PtFx> fx = new List<PtFx>(); double Y = 0; for (double X = -20; X <= 20; X += 0.01) { Y = (Math.Pow(Math.E, (5 * X - 3 * (Math.Pow(X, 2))))) - (Math.Pow(X, 3)); fx.Add(new PtFx(X, Y)); } controlGrafico1.SetPuntiFx(fx, 0);
+ Fine Articolo.