Costruire grafici 2D e 3D con ASP.NET

di Stefano Mostarda, in ASP.NET,
  • 0
  • 0
  • 0
  • 33,71 KB

Uno dei tanti punti di forza del .NET Framework è la semplicità con cui si possono creare immagini, punti, linee o ellissi.

Unendo tutto questo si possono ottenere dei grafici, che sono una parte essenziale delle applicazioni che gestiscono grosse quantità di dati. Esistono diverse tipologie di grafici atte a soddisfare diverse esigenze. Ad esempio, se si vuole analizzare la ripartizione dei costi o dei ricavi di un'azienda, un grafico a torta è la soluzione ideale; se si vuole analizzare l'andamento di un titolo in borsa, un grafico a linee e quello che meglio si presta a soddisfare l'esigenza. In questo articolo vedremo come creare alcuni dei più diffusi tipi di grafici.

La base di lavoro

Il namespace System.Drawing contiene tutte le classi di cui abbiamo bisogno. Alla base di tutto il meccanismo ci sono 2 classi: Bitmap e Graphics . La prima rappresenta l'area di lavoro e possiamo paragonarla alla tela di un pittore che si accinge a dipingere un quadro, mentre la seconda da' la possibilità di disegnare qualunque cosa sulla nostra area, un po' come la tavolozza del pittore.

const int width = 200, height = 200;
Bitmap bitmap = new Bitmap(width, height);

Per prima cosa dobbiamo creare l'area di lavoro, detta anche Canvas, per il nostro grafico, impostandone le dimensioni:

Graphics graphics = Graphics.FromImage(bitmap)

Successivamente diamo il Canvas in input ad un oggetto Graphics dando così la possibilità di disegnarci sopra.

C'è inoltre un concetto chiave che va specificato all'inizio: il punto che corrisponde alle coordinate {0, 0} è il punto in alto a sinistra del Canvas. Questo significa che aumentando i valori dell'asse X il disegno apparirà più a destra, mentre aumentando i valori dell'asse Y il disegno apparirà più in basso.

Grafici a torta

Il grafico a torta è la forma più usata per visualizzare informazioni sulla ripartizione di una determinata risorsa. Lo stesso Windows usa questo tipo di grafico nella finestra delle proprietà del disco.

La creazione di questa tipologia di grafico è supportata in maniera nativa dal framework. Una volta disegnato un rettangolo nell'area di lavoro, utilizziamo il metodo FillPie , della classe Graphics, per disegnare le fette che compongono il grafico.

//Disegna un rettangolo delle dimensioni dell'immagine riempendolo di bianco
graphics.FillRectangle(new SolidBrush(Color.White), 0, 0, width, height);
//Istanzia la connessione al db
using (OleDbConnection conn = new OleDbConnection(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Request.PhysicalApplicationPath + "db.mdb;Persist Security Info=False")){
  //Istanzia gli oggetti per eseguire la query
  conn.Open();
  OleDbCommand cm = new OleDbCommand("SELECT SUM(qta) FROM torta", conn);
  float totale = Convert.ToInt32(cm.ExecuteScalar());
  cm.CommandText = "SELECT descrizione, qta, colore FROM torta";
  OleDbDataReader rd = cm.ExecuteReader();
  float AngoloIniziale = 0;
  //Per ogni record calcola l'angolo della fetta e la disegna
  while (rd.Read()) {
    float AngoloCirc = 360 * Convert.ToSingle(rd["QTA"]) / totale;
    graphics.FillPie(new SolidBrush(ColorTranslator.FromHtml(Convert.ToString(rd["COLORE"]))), 0, 0, width, height, AngoloIniziale, AngoloCirc);
    AngoloIniziale += AngoloCirc;
  }
}

In questo snippet, viene innanzitutto stabilita una connessione al database e calcolato l'importo totale che il grafico intero rappresenta. Successivamente viene creato un DataReader con i dati relativi ai singoli importi (colore, importo). Scorrendo il DataReader, l'importo della riga viene convertito in gradi e il valore viene passato in input al metodo che disegna la fetta ( FillPie ). Questo metodo accetta in input il colore della fetta, le coordinate del punto in alto a sinistra, larghezza e altezza del rettangolo che contiene il cerchio (in questo caso, trattandosi di una circonferenza le dimensioni coincidono creando così un quadrato), l'angolo iniziale e la sua ampiezza.

Seppur funzionale, il precedente esempio crea un grafico 2D e quindi non bellissimo da vedere.

Per creare un qualcosa dall'aspetto più gradevole, si può ricreare l'effetto 3D. Nel .NET framework, purtroppo, non esiste nulla di già pronto, quindi dobbiamo simulare l'effetto attraverso alcuni passi:

  • Trasformare la circonferenza in una ellisse. Questo comporta anche il ricalcalo degli angoli di incidenza in base alla formula dell'ellisse
  • Disegnare una seconda ellisse spostata rispetto alla precedente dando così l'effetto 3D. La cosa più importante da tenere a mente quando si sovrappongono due immagini è l'ordine in cui queste vengono disegnate. Infatti, ogni oggetto disegnato viene messo in primo piano, facendo passare in secondo quello a cui si sovrappone.
  • Ritoccare l'area della seconda ellisse allineando i bordi.

I passi 1 e 3 necessitano di conoscenze di trigonometria quindi ho usato delle funzioni per rendere il tutto trasparente.

while (rd.Read()){
  float AngoloCirc = 360 * Convert.ToSingle(rd["QTA"]) / totale;
  float AngoloEllisse = 0;
  float AngoloFinale = AngoloIniziale + AngoloCirc;
  if (AngoloFinale > 360) AngoloFinale = 360;
  if (AngoloCirc % 180 != <st1:metricconverter ProductID="0F" w:st="on">0F</st1:metricconverter>) AngoloEllisse = ConvertiAngolo(AngoloFinale) - ConvertiAngolo(AngoloIniziale);
  if (AngoloEllisse < 0) AngoloEllisse += 360;
  if ((ConvertiAngolo(AngoloIniziale) + AngoloEllisse) > 360) AngoloEllisse = 360 - ConvertiAngolo(AngoloIniziale);
  FetteUp.Add(new Fetta(AngoloIniziale, AngoloCirc, ConvertiAngolo(AngoloIniziale), AngoloEllisse, ColorTranslator.FromHtml(Convert.ToString(rd["COLORE"]))));

  if (AngoloIniziale < 180){
   double Radianti = AngoloIniziale * Math.PI / 180;
   PointF p = new PointF((width / 2) + (float)((width / 2) * Math.Cos(Radianti)), (height / 2) + (float)((height / 2) * Math.Sin(Radianti)));
   PointF p1 = new PointF(p.X, p.Y + pieHeight);
   if (AngoloFinale < 180)
    Radianti = (AngoloFinale) * Math.PI / 180;
   else
    Radianti = Math.PI;
   PointF p2 = new PointF((width / 2) + (float)((width / 2) * Math.Cos(Radianti)), (height / 2) + (float)((height / 2) * Math.Sin(Radianti)));
   PointF p3 = new PointF(p2.X, p2.Y + pieHeight);
   BordiUp.Add(new Bordo(p, p2, p3, p1, ColorTranslator.FromHtml(Convert.ToString(rd["COLORE"]))));

   FetteDown.Add(new Fetta(AngoloIniziale, AngoloCirc, ConvertiAngolo(AngoloIniziale), AngoloEllisse, ColorTranslator.FromHtml(Convert.ToString(rd["COLORE"]))));
  }
  AngoloIniziale += AngoloCirc;
}
rd.Close();

foreach(Fetta f in FetteDown) graphics.FillPie(new HatchBrush(HatchStyle.Percent50, f.Colore), 0, pieHeight, width, height, f.AngoloInizialeEllisse, f.AngoloEllisse);
foreach(Bordo b in BordiUp) graphics.FillPolygon(new HatchBrush(HatchStyle.Percent50, b.Colore), new PointF[] { b.P1, b.P2, b.P3, b.P4 });
foreach(Fetta f in FetteUp) graphics.FillPie(new SolidBrush(f.Colore), 0, 0, width, height, f.AngoloInizialeEllisse, f.AngoloEllisse);

Come detto sopra, a differenza del grafico in 2D, questo non viene rappresentato con una circonferenza perfetta, ma con un ellisse e questo comporta un calcolo diverso degli angoli. Quindi la prima cosa da fare è creare una funzione (ConvertiAngolo) che accetta in input l'angolo in base alla circonferenza e restituisce l'angolo in base all'ellisse. Invece che disegnare direttamente la fetta in primo piano, i dati vengono memorizzati in una collection tipizzata così da poterla disegnare, in un secondo momento, nel corretto ordine. Le fette dell'ellisse in "secondo piano", vengono disegnate solo se l'angolo iniziale è compreso tra 0 e 180. Anche in questo caso, i dati vengono memorizzati in una collection per disegnare il tutto successivamente. Sovrapponendo le ellissi, si ha l'effetto 3D, ma i bordi non vengono definiti.

Immagine

La soluzione a questo problema è disegnare un rettangolo che abbia come vertici i 4 punti in cui i bordi delle fette toccano l'ellisse e riempire il rettangolo con lo stesso colore dell'ellisse in secondo piano. Anche in questo caso i dati sono memorizzati in una collection.

A questo punto abbiamo tutti i dati necessari per poter procedere al disegno della torta nel modo più corretto: prima viene disegnata la parte inferiore, poi vengono disegnati i rettangoli per eliminare le discrepanze tra le fette delle 2 ellissi ed infine viene disegnata l'ellisse superiore.

Immagine

3 pagine in totale: 1 2 3

Attenzione: Questo articolo contiene un allegato.

Contenuti dell'articolo

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti