Grafikk
Børre Stenseth
Moduler>Grafikk>Histogram

Enkelt histogram

Hva
screen
Et enkelt eksempel produksjon av bildefiler

I denne modulen skal vi gjøre et par enkle øvelser med tegning, kopiering av bitmaps og lagring av bilder.

Vi gjør dette med et eksempel der vi tar for oss en rekke med heltall, lager et enkelt histogram og sparer histogrammet på fil som et gif-bilde.

Logikken i programmet er slik som vi valigvis gjør når vi skal tegne. Tegningen er knyttet til paint-metoden i det panelet vi tegner i og vi provoserer fram tegning ved å invalidere panelet. I dette enkle eksempelet er det to begivenheter som provoserer fram en tegning av histogrammet:

  • Vi klikker på Draw-button
  • Vi endrer størrelsen på vinduet, resize

Denne logikken går fram av følgende kode:

List<int> data=null;
public Form1()
{
    InitializeComponent();
    data = new List<int>(100);
}
private void buttonDaw_Click(object sender, EventArgs e)
{
    String[] lines = textBox1.Text.Split('\r');
    data=new List<int>(lines.Length);
    try
    {
        foreach (string line in lines)
            if (line.Trim().Length > 0)
                data.Add(Convert.ToInt32(line.Trim()));
    }
    catch (Exception ex)
    {
        labelMsg.Text = "feil format";
    }
    // force a redraw
    panel1.Invalidate();
}

private void panel1_Resize(object sender, EventArgs e)
{
    // we force a redraw when panel is sized
    panel1.Invalidate();
}

Vi kan lage bildet etter to strategier:

  • Vi kan tegne i panelet, kopiere denne tegningen til en bitmap som vi så lagrer i det formatet vi ønsker
  • Vi kan tegne direkte i en bitmap (ikke på skjermen), eventuelt kopiere til skjermen for å inspisere, og så lage bitmapen direkte.

Nedenfor finner du kode for begge strategiene

Tegne på skjermen

Vi tegner på skjermen, i panelet og kopierer til en bitmap som vi så lagrer.. Selve tegnerutina, drawData, er sitert mot slutten av modulen.

private void panel1_Paint(object sender, PaintEventArgs e)
{
    // we draw on the panel and copy it to a bitmap
    // then we save it
    Rectangle Rec = new Rectangle(0, 0, panel1.Width, panel1.Height);
    
    drawData(e.Graphics, Rec, data);
    // copy the drawing to a bitmap
    Bitmap canvas = new Bitmap(Rec.Width, Rec.Height);
    Graphics bg = Graphics.FromImage(canvas);
    // copy from screen, in screen coordinates
    Rectangle screenR = panel1.RectangleToScreen(Rec);
    bg.CopyFromScreen(new Point(screenR.X, screenR.Y), 
        new Point(0, 0), 
        new Size(screenR.Width,screenR.Height));
    // save the drawing
   canvas.Save(Path.Combine(Application.StartupPath, "bildetest.gif"));
}

Tegne direkte på en bitmap

Vi tegner til en bitmap og lagrer den. I eksempelet kopierer vi bitmapen til skjermen for å inspisere, men det er ikke nødvendig for å lage bildet..

private void panel1_Paint(object sender, PaintEventArgs e)
{
    // we draw on a bitmap and copies this bitmap to the panel
    // then we save it
    Rectangle Rec = new Rectangle(0, 0, panel1.Width, panel1.Height);
    Bitmap canvas = new Bitmap(Rec.Width, Rec.Height);
    Graphics cg = Graphics.FromImage(canvas);
    // set white background
    cg.FillRectangle(Brushes.White,Rec);
    
    drawData(cg, Rec,data);
  
    // copy the drawing to the panel
    e.Graphics.DrawImage(canvas,new Point(0,0));
    
    // save the drawing
    canvas.Save(Path.Combine(Application.StartupPath, "bildetest.gif"));
}

Selve uttegningen er gjort slik:

// draw data within Rec on g
private void drawData(Graphics g,Rectangle Rec,List<int>data)
{
    Font tFont = new Font("Arial", 16);
    SolidBrush blueBrush = new SolidBrush(Color.Blue);
    // any data to display
    if (data.Count == 0)
    {
        g.DrawString(" Ingen data ",
            tFont,
            blueBrush,
            new Point(10, 4));
    }
    else
    {
        // find max value
        float max = 0.0F;
        foreach (int number in data)
            max = Math.Max((float)number, max);
        
        // Width of each column, with some margins
        int w = 
            (Rec.Width - 10 * data.Count - 20) / data.Count;
         // flip round top and leave some room at top for text
        int txtheight = 20;
        g.ResetTransform();
        g.TranslateTransform(0.0F, Rec.Height);
        g.ScaleTransform(1.0F,
            -1.0F * ((Rec.Height - txtheight) / (max + 1)));
        for (int ix = 0; ix < data.Count; ix++)
        {
            g.FillRectangle(Brushes.Red,
                new Rectangle(10 + ix * (w + 10), 0, w, data[ix]));
        }
        
        // text it 
        g.ResetTransform();
        g.DrawString("Resultater", tFont, blueBrush, new Point(10, 4));
    }
    g.Flush();
}

Dette kan selvsagt gjøre på mange forskjellige måter. I slik enkle tilfeller som dette kan det kanskje være en avveining om vi skal tegne direkte i pikselkoordinater eller om vi skal transformere fra et annet koordinatsystem slik jeg har gjort.

Hvis du studerer "System.Drawing.Imaging.ImageFormat" i dokumentasjonen vil du finne at du kan spare bitmaps i en rekke andre formater enn gif.

Referanser
  • Prosjekt:
    https://svn.hiof.no/svn/psource/Csharpspikes/histo1
  • Modulen Webhistogram demonstrerer tilsvarende grafikk på en vevside.
  • Modulen Histogram demonstrerer tilsvarende grafikk som en AJAX-løsning.
Vedlikehold

B. Stenseth, jan 2008

(Velkommen) Moduler>Grafikk>Histogram (Googlegraf)