Grafikus C# – Windows Forms tanfolyam › 9.11 lecke

Windows Forms · 9.11 lecke · grafikon adatokból

Grafikon adatbázisból: oszlopok, színek, tengelycímek

A grafikonos leckében beégetett számokból rajzoltál. Most az adatbázis összesítéséből készítesz grafikont: egy SQL-lekérdezés eredményét sorról sorra átteszed a Chart vezérlőbe, kiemelsz egy oszlopot színnel, és feliratokat adsz a tengelyeknek.

  • kb. 90 perc
  • közepes szint
  • Chart és SQL
Diák C# programozást gyakorol egy tanulósarokban

A lecke végére

  • egy GROUP BY lekérdezés eredményét Chart oszlopokká alakítod;
  • egy kiválasztott oszlopot külön színnel kiemelsz (Points[index].Color);
  • címet és tengelyfeliratokat adsz a grafikonnak;
  • a grafikont újrarajzoltatod, ha a felhasználó másik elemet választ;
  • felismered a tipikus hibákat (kétszer rajzolt sorozat, üres tábla).

Mielőtt elkezded

Ehhez a leckéhez kell a 9.9 lecke (Chart, Series, AddXY, Titles), a 3.12 lecke (SUM, GROUP BY), a 3.8 lecke (DataTable, SQLiteDataAdapter) és az 1.6 lecke (ComboBox).

← 9.9 Grafikonok és adatok szemléltetése

Előkészítés

Ugyanazt a gyakorlo.db adatbázist használjuk, amelyet a 3.12 leckében hoztál létre (Dolgozok tábla). Ha nincs meg, ott megtalálod a létrehozó SQL-t.

  1. Hozz létre új Windows Forms (.NET Framework) projektet GrafikonDb néven, telepítsd a System.Data.SQLite csomagot (3.2), és add hozzá a DB.cs osztályt "gyakorlo.db" fájlnévvel.
  2. Másold a gyakorlo.db fájlt a bin\Debug mappába.
  3. A Formra tegyél egy Chart vezérlőt (chart1, Dock = Fill) és fölé egy ComboBox-ot (cmbReszleg, DropDownStyle = DropDownList). A ComboBox elemei: Iroda, Raktár, Műhely.
  4. A fájl tetejére írd be:
Form1.csC#
using System.Data;
using System.Data.SQLite;
using System.Drawing;
using System.Windows.Forms.DataVisualization.Charting;

Egy lekérdezés eredménye egy sorozat lesz

A terv három lépés: (1) az SQL kiszámolja részlegenként a fizetések összegét, (2) az eredmény egy DataTable-ben érkezik, (3) a tábla sorain végigmenve minden sor egy oszlop lesz a grafikonon.

Form1.csC#
private void Rajzol()
{
string sql = "SELECT reszleg, SUM(fizetes) AS osszeg FROM Dolgozok GROUP BY reszleg ORDER BY reszleg";

try
{
DataTable tabla = new DataTable();

using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
using (SQLiteDataAdapter adapter = new SQLiteDataAdapter(sql, kapcsolat))
{
adapter.Fill(tabla);
}

chart1.Series.Clear();

Series sorozat = chart1.Series.Add("Fizetések összege");
sorozat.ChartType = SeriesChartType.Column;
sorozat.IsValueShownAsLabel = true;

foreach (DataRow sor in tabla.Rows)
{
string reszleg = sor["reszleg"].ToString();
int osszeg = Convert.ToInt32(sor["osszeg"]);

sorozat.Points.AddXY(reszleg, osszeg);
}
}
catch (Exception ex)
{
MessageBox.Show("Hiba: " + ex.Message);
}
}

private void Form1_Load(object sender, EventArgs e)
{
Rajzol();
}
chart1.Series.Clear()Újrarajzolás előtt kitöröljük a régi sorozatot. Ha ezt kihagyod, minden hívás után újabb sorozat kerül a grafikonra.
foreach (DataRow sor in tabla.Rows)A tábla minden során végigmegyünk, és a sor két értékéből egy pontot készítünk.
sor["osszeg"]Az SQL-ben adott alias (AS osszeg) az oszlop neve a DataTable-ben. Az érték object típusú, ezért alakítjuk át a Convert.ToInt32-vel.
Points.AddXY(x, y)Az X az oszlop felirata (a részleg neve), az Y az oszlop magassága (az összeg).

Jósolj!

Hány oszlop lesz a grafikonon, milyen sorrendben, és milyen magasak? (Az SQL ORDER BY reszleg betűrendbe rendezi a sorokat.)

Jóslat ellenőrzése

Három oszlop lesz, betűrendben: Iroda 1 165 000, Műhely 860 000, Raktár 935 000. A számok az oszlopok fölött is látszanak, mert az IsValueShownAsLabel igaz.

Egy oszlop kiemelése színnel

Eddig minden oszlop azonos színű volt. A grafikon akkor segít, ha a lényeges oszlop kiemelkedik. Egy sorozat minden pontjának külön színe lehet: a Points.AddXY az új pont indexét adja vissza, azzal elérhető a pont, és állítható a Color tulajdonsága.

Form1.csC#
private void Rajzol(string kivalasztott)
{
// ... lekérdezés és DataTable feltöltése, mint fent ...

foreach (DataRow sor in tabla.Rows)
{
string reszleg = sor["reszleg"].ToString();
int osszeg = Convert.ToInt32(sor["osszeg"]);

int index = sorozat.Points.AddXY(reszleg, osszeg);

if (reszleg == kivalasztott)
{
sorozat.Points[index].Color = Color.OrangeRed;
}
else
{
sorozat.Points[index].Color = Color.SlateBlue;
}
}
}
int index = ...AddXY(...)Az AddXY visszatérési értéke a most hozzáadott pont sorszáma a sorozatban.
sorozat.Points[index].ColorAz adott pont színe. A Color.OrangeRed és a Color.SlateBlue a 9.4 leckéből ismert színnevek.
reszleg == kivalasztottA kiválasztott részlegnév esetén piros-narancs az oszlop, a többi kékes-lila.

Rövidebben, feltételes operátorral

Az if / else helyett írhatod egyetlen sorban: sorozat.Points[index].Color = reszleg == kivalasztott ? Color.OrangeRed : Color.SlateBlue;. Ugyanazt csinálja, csak tömörebb.

Cím és tengelyfeliratok

Egy grafikon csak akkor érthető, ha kiderül belőle, mit mér és mi van a tengelyeken. A címet a Titles gyűjteménnyel adtad meg a 9.9 leckében, a tengelyek feliratát pedig a ChartAreas[0] tengelyeinek Title tulajdonsága tartalmazza.

Form1.cs – a Rajzol elejére, a Series.Clear() utánC#
chart1.Titles.Clear();
chart1.Titles.Add("Fizetések összege részlegenként");

chart1.ChartAreas[0].AxisX.Title = "Részleg";
chart1.ChartAreas[0].AxisY.Title = "Összeg (Ft)";
Titles.Clear()Ugyanaz a helyzet, mint a sorozatnál: minden újrarajzolásnál új cím kerülne a régi mellé, ezért előbb töröljük a meglévőket.
AxisX.TitleA vízszintes tengely felirata. Itt a részlegek nevei állnak, ezért a felirat: Részleg.
AxisY.TitleA függőleges tengely felirata. A mértékegységet is írd bele (Ft), különben senki nem tudja, mit jelentenek a számok.
ChartAreas[0]A grafikon rajzterülete. Egy egyszerű grafikonon egy van belőle, a 0. sorszámú.

Próbáld ki!

Írd be a négy sort, futtasd, és nézd meg, hol jelennek meg a feliratok. Utána írd át a címet úgy, hogy a kiválasztott részleg neve is benne legyen ("Fizetések összege – kiemelve: " + kivalasztott).

Frissítés, amikor a felhasználó választ

Eddig a grafikon csak az ablak megnyitásakor rajzolódott. Most kössük a ComboBox-hoz: ha másik részleget választasz, a kiemelés átkerül a másik oszlopra. Ehhez a cmbReszleg SelectedIndexChanged eseményét használjuk (dupla kattintás a ComboBoxon a tervezőben).

Form1.cs – a teljes, végső változatC#
private void Rajzol(string kivalasztott)
{
string sql = "SELECT reszleg, SUM(fizetes) AS osszeg FROM Dolgozok GROUP BY reszleg ORDER BY reszleg";

try
{
DataTable tabla = new DataTable();

using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
using (SQLiteDataAdapter adapter = new SQLiteDataAdapter(sql, kapcsolat))
{
adapter.Fill(tabla);
}

chart1.Series.Clear();
chart1.Titles.Clear();
chart1.Titles.Add("Fizetések összege részlegenként");
chart1.ChartAreas[0].AxisX.Title = "Részleg";
chart1.ChartAreas[0].AxisY.Title = "Összeg (Ft)";

Series sorozat = chart1.Series.Add("Fizetések összege");
sorozat.ChartType = SeriesChartType.Column;
sorozat.IsValueShownAsLabel = true;

foreach (DataRow sor in tabla.Rows)
{
string reszleg = sor["reszleg"].ToString();
int osszeg = Convert.ToInt32(sor["osszeg"]);

int index = sorozat.Points.AddXY(reszleg, osszeg);

if (reszleg == kivalasztott)
{
sorozat.Points[index].Color = Color.OrangeRed;
}
else
{
sorozat.Points[index].Color = Color.SlateBlue;
}
}
}
catch (Exception ex)
{
MessageBox.Show("Hiba: " + ex.Message);
}
}

private void cmbReszleg_SelectedIndexChanged(object sender, EventArgs e)
{
Rajzol(cmbReszleg.Text);
}

private void Form1_Load(object sender, EventArgs e)
{
cmbReszleg.SelectedIndex = 0;
}

Jósolj!

Az indításkor a Form1_Load csak a SelectedIndex = 0 sort futtatja. Rajzolódik-e ettől grafikon, vagy a Rajzol-t is meg kell hívni?

Jóslat ellenőrzése

Rajzolódik. A SelectedIndex átállítása kiváltja a SelectedIndexChanged eseményt, az pedig meghívja a Rajzol-t az Iroda névvel, így az első oszlop lesz kiemelve. Ha a Rajzol-t is beírnád a Form1_Load-ba, a grafikon kétszer készülne el.

Gyakori hibák

1. Hiányzó Series.Clear()

Ha a Rajzol elejéről kihagyod a chart1.Series.Clear() sort, a második hívásnál (másik elem választásakor) a régi sorozat a grafikonon marad, és mellé kerül egy új, vagy ugyanazon a néven felvett sorozatnál hibaüzenettel leáll a program. A Titles.Clear() hiánya ugyanígy halmozza a címeket.

Megoldás: újrarajzolás előtt mindig töröld a sorozatokat és a címeket.

2. A tábla nem ismeri az oszlopnevet

Column 'osszeg' does not belong to table .

Az SQL-ben nem adtál aliast (SUM(fizetes) AS osszeg nélkül), így az oszlop neve SUM(fizetes) lett, a C# kódban pedig sor["osszeg"] szerepel.

Megoldás: az SQL-ben adott alias és a C# kódban használt név legyen pontosan ugyanaz.

3. NULL az összegben

Object cannot be cast from DBNull to other types.

Ha egy csoportban minden fizetés hiányzik (NULL), a SUM eredménye is NULL, a Convert.ToInt32 pedig nem tud mit kezdeni vele (lásd 3.12).

Megoldás: COALESCE(SUM(fizetes), 0) AS osszeg az SQL-ben.

Alkoss

Dolgozz ugyanabban a projektben. Először próbáld ki magad, a megoldást csak utána nyisd le.

1. feladat – Létszám grafikon

Készíts új gombot (btnLetszam), amely részlegenként a dolgozók számát rajzolja oszlopdiagramra. A cím legyen Létszám részlegenként, az Y tengely felirata Fő.

Megoldás
Form1.csC#
private void btnLetszam_Click(object sender, EventArgs e)
{
string sql = "SELECT reszleg, COUNT(*) AS letszam FROM Dolgozok GROUP BY reszleg ORDER BY reszleg";

try
{
DataTable tabla = new DataTable();

using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
using (SQLiteDataAdapter adapter = new SQLiteDataAdapter(sql, kapcsolat))
{
adapter.Fill(tabla);
}

chart1.Series.Clear();
chart1.Titles.Clear();
chart1.Titles.Add("Létszám részlegenként");
chart1.ChartAreas[0].AxisX.Title = "Részleg";
chart1.ChartAreas[0].AxisY.Title = "Fő";

Series sorozat = chart1.Series.Add("Létszám");
sorozat.ChartType = SeriesChartType.Column;
sorozat.IsValueShownAsLabel = true;

foreach (DataRow sor in tabla.Rows)
{
sorozat.Points.AddXY(sor["reszleg"].ToString(), Convert.ToInt32(sor["letszam"]));
}
}
catch (Exception ex)
{
MessageBox.Show("Hiba: " + ex.Message);
}
}

2. feladat – A legnagyobb oszlop kiemelése

Módosítsd az összeg-grafikont úgy, hogy ne a ComboBox-ban kiválasztott, hanem mindig a legnagyobb összegű részleg oszlopa legyen piros-narancs. Tipp: rendezd az SQL eredményét csökkenő sorrendbe, és figyeld a ciklus első körét.

Megoldás

Az SQL-ben ORDER BY osszeg DESC a legnagyobbat teszi az első helyre. A ciklusban az első sor indexe 0, azt színezzük ki.

Form1.cs – a ciklusC#
// SQL: ... GROUP BY reszleg ORDER BY osszeg DESC

foreach (DataRow sor in tabla.Rows)
{
string reszleg = sor["reszleg"].ToString();
int osszeg = Convert.ToInt32(sor["osszeg"]);

int index = sorozat.Points.AddXY(reszleg, osszeg);

if (index == 0)
{
sorozat.Points[index].Color = Color.OrangeRed;
}
else
{
sorozat.Points[index].Color = Color.SlateBlue;
}
}

3. feladat – Küszöb számlálóval

Tegyél a Formra egy NumericUpDown-t (nudKuszob, Maximum = 5000000, Increment = 100000). Az összeg-grafikonon csak azok a részlegek jelenjenek meg, amelyek összege legalább a küszöb.

Megoldás

A szűrést a ciklusban végezzük: csak akkor adunk pontot a sorozathoz, ha az összeg eléri a küszöböt. Az érték változásakor újra kell rajzolni (ValueChanged esemény).

Form1.csC#
// a Rajzol ciklusában:
int kuszob = (int)nudKuszob.Value;

foreach (DataRow sor in tabla.Rows)
{
string reszleg = sor["reszleg"].ToString();
int osszeg = Convert.ToInt32(sor["osszeg"]);

if (osszeg >= kuszob)
{
int index = sorozat.Points.AddXY(reszleg, osszeg);
sorozat.Points[index].Color = Color.SlateBlue;
}
}

private void nudKuszob_ValueChanged(object sender, EventArgs e)
{
Rajzol(cmbReszleg.Text);
}

4. feladat – Átlagfizetés

Rajzolj grafikont a részlegek átlagfizetéséről (AVG). Figyelj arra, hogy az átlag nem biztos, hogy egész szám.

Megoldás

Az AVG tizedestörtet adhat, ezért Convert.ToDouble-t használunk, a szám megjelenését pedig a LabelFormat tulajdonsággal rendezzük, hogy ne legyen hosszú tizedestört a felirat.

Form1.csC#
string sql = "SELECT reszleg, AVG(fizetes) AS atlag FROM Dolgozok GROUP BY reszleg ORDER BY reszleg";

// ... DataTable feltöltése, Series.Clear() ...

Series sorozat = chart1.Series.Add("Átlagfizetés");
sorozat.ChartType = SeriesChartType.Column;
sorozat.IsValueShownAsLabel = true;
sorozat.LabelFormat = "N0";

foreach (DataRow sor in tabla.Rows)
{
double atlag = Convert.ToDouble(sor["atlag"]);
sorozat.Points.AddXY(sor["reszleg"].ToString(), atlag);
}

Ellenőrizd magad

  • Tudom, hogyan lesz egy DataTable sorából Chart pont.
  • Tudom, hogy az AddXY visszaadja az új pont indexét, és azzal színezhetem a pontot.
  • Be tudom állítani a címet és a két tengely feliratát.
  • Újrarajzolás előtt törlöm a sorozatokat és a címeket.
  • Tudom, miért kell alias az SQL-ben, és mit kezd a C# kód vele.

Visszatérés a tanmenethez

A grafikon adatbázisból elkészült. A tanmenetben megtalálod a folytatást és az összetett gyakorlófeladatokat.

Tanmenet