TAChart Tutorial: Userdefined ChartSource/fi
│
English (en) │
suomi (fi) │
Johdanto
Tässä opetusohjelmassa käytämme TAChart-komponentteja piirtämään ikäjakauman maailman väestöstä. Näin tehdessään, opit
- miten työskennellä aluekuvaajan kanssa
- ja ennen kaikkea, miten soveltaa käyttäjän määrittämää dataa kaaviossa
Kuten tavallista, oletamme, että olet perehtynyt Lazarukseen ja Object Pascal kieleen ja on joitakin perustietoja TAChart paketista, jotka voit käymällä läpi Aloittajan opetusohjelman.
Esivalmistelu
Data
Saitti www.census.gov sisältää runsaasti demografista tietoa. Tämä linkki vie suoraan tietoihin, jotka halutaan piirtää: maailman väestö iän ja sukupuolen mukaan.
Mene sivulle, valitse ja klikkaa "submit" (lähetä). Ikkunan alaosassa, näet taulukon sarakkeet "Age", "Both Sexes Population", "Male population", "Female population" ja "Sex ratio". Valitse hiirellä taulukon tiedot (mukaan lukien otsikko) ja kopioi ne leikepöydälle. Liitä tiedot tekstieditoriin ja tallenna nimellä "population.txt".
Avaa Lazarus ja luo uusi projekti. Ensin meidän täytyy ladata tietoja, jotta ne voidaan piirtää.
Määritellään tietue TPopulationRecord
ja taulukko TPopulationArray
joka pitää väestön tiedot taulukossa:
type
TPopulationRecord = record
Age: Integer;
Total: Double;
Male: Double;
Female: Double;
Ratio: Double;
end;
TPopulationArray = array of TPopulationRecord;
Seuraavaksi luetaan tiedosto. Tutustu käännösyksikköön (unit) population.pas
lähdekoodiin. Löydät sieltä:
procedure LoadPopulationData(const AFilename:string; var Data:TPopulationArray)
mikä tekee tiedoston lukemisen ja tallentaa datan TPopulationArray
taulukkoon.
Sisällytä käännösyksikkö (unit) population
uses
-lauseen luetteloon jotta päästään käsiksi kyseiseen aliohjelmaan. Lisää muuttuja PopulationData
joka on tyypiltään TPopulationArray
lomakkeen luokan private
osioon.
Nyt meillä on kaikki valmiina jotta voidaan kutsua LoadPopulationData
lomakkeen OnCreate tapahtumasta. Tämä lukee tiedoston tiedot taulukkoon PopulationData
.
uses
..., population;
type
TForm1 = class(TForm)
// ...
private
PopulationData : TPopulationArray;
// ...
end;
const
POPULATION_FILE = 'population.txt';
procedure TForm1.FormCreate(Sender: TObject);
begin
LoadPopulationData(POPULATION_FILE, PopulationData);
end;
Käännä tarkistaaksesi ilmeiset virheet pois. Voit saada ajonaikaisen virheen (runtime error), että tiedosto ei löydy. Tämä johtuu siitä, että tiedosto oletetaan sijaitsevan samassa kansiossa kuin ajettava tiedosto (ns. exe-tiedosto). Voit korjata tämän, joko muuttamalla vakio POPULATION_FILE
osoittamaan oikeaan tallennuspaikkaan, tai kopioida "population.txt" on exe-kansioon. Nyt ohjelma pitäisi toimia hyvin, näet vaikka näet vain tyhjän lomakkeen.
Yhdistelmäruutu ComboBox ryhmän valitsimena
Tiedosto sisältää eri ryhmiä: koko, miespuolinen ja naispuolinen väestö sekä mies-nais suhde. Miksi se ei näytä näitä tietoja meidän kaaviossa?
Tehdään tämä pohjaksi
- Lisätään paneeli (TPanel-komponentti) lomakkeelle. Asetetaan sen align arvoon
alTop
ja poistetaan siinä oleva teksti. - Lisätään ComboBox tähän paneelin. Asetetaan sen
Style
arvooncsDropdownList
, lisätään tekstit "Kokonaisväkiluku", "Miesväestö", "Naisväestö" ja "Suhde mies / nainen (%)"Items
omaisuuteen sekä asetetaanItemIndex
arvoon 0.
Valmistellaan TChart
Nyt on esivalmistelut tehty. On aika valmistella kaaviota...
- Lisää
TChart
komponentti lomakkeelle, aseta sen align arvoonalClient
. - Aseta kaavion
BackColor
arvoonclWhite
ja kummankin akselinGrid.Color
arvoonclSilver
. - Laita kummankin akselin otsikot näkyviin asettamalla
LeftAxis.Visible
jaBottomAxis.Visible
arvoontrue
. - Kirjoita bottom akselin
Caption
ominaisuuteen joko "Age" tai "Ikä" ja left akselille "Total population" (tai jätä tyhjäksi - myöhemmin se korvataan kyseisen suorituksen mukaisen valinnan mukaan mikä on ComboBox:ssa valittuna ) - Kirjoita teksti "World population" tai "Maailman väestö" kaavion otsikoksi.
- Laita näiden fontin tyylit arvoon
fsBold
. - Ehkä olisi hyvä idea näyttää viittaus mistä tiedot ovat peräisin. Footer - eli kaavion ominaisuus
Foot
voidaan käyttää tähän. Huomaa, että omaisuuteen Foot.Text (samoin kuin Title.Text ) voidaan merkkijonojen muokkaimen avulla kirjoittaa moni rivisiä otsikoita.
Luodaan aluekuvaaja
Tällä kertaa näytetään tiedot alue kuvaajan avulla. Tämän tyyppinen kuvaaja yhdistää datapisteet, kuten viivakuvaaja, mutta myös täyttää alueen tietyllä värillä. On hyvin yleistä näyttää demografiset tiedot, kuten väestöpyramidi , tällä tavalla.
Joten, lisää TAreaSeries
(aluekuvaaja) kaavioon. Kuten tavallista, et näe sitä tällä hetkellä, koska se ei ole vielä mitään tietoa. Mutta voimme muokata sen joitakin ominaisuuksia:
- Aseta sen
SeriesColor
arvoonclSkyBlue
. Tämä on aluekuvaajan täyttöväri. - Piilota pystysuorat viivat asettamalla
AreaLinesPen
:nStyle
arvoonpsClear
. - Jos haluat, voit myös vaihtaa
ConnectType
arvoonctStepXY
taictStepYX
saadaksesi askel efektin.
Myöhemmin, kun kuvaajalla on dataa, voit tutustua näihin ja muihin ominaisuuksiin paremmin ja nähdä mitä ne tekevät.
Käyttäjän määrittämää dataa kaavioon
Miten saadaan oma data aluekuvaajalle?
Ensimmäinen ajatus ehkä on käydä läpi PopulationData
taulukko ja kopioida data kuvaajalle käyttäen AddXY
kutsua, näin:
var
i: Integer;
begin
for i:=0 to High(PopulationData) do
Chart1AreaSeries1.AddXY(PopulationData[i].Age, PopulationData[i].Total);
end;
Haittana tässä lähestymistavassa on, että dataa olisi muistissa kahdesti: PopulationData
taulukossa ja kuvaajassa - ei kovin hyvä ratkaisu: se tuhlaisi muistia ja lisäksi olisi sovitettava aina myös toisen varaston dataa kun dataa lisätään, poistetaan tai muokataan.
Tämän välttämiseksi lisäämme TUserDefinedChartSource
lomakkeelle, se on kolmas tai neljäs kuvake vasemmalta Chart komponenttipaletilla. Tämä kaavio lähde on tehty liitännäksi kaikenlaisten tietojen varastoinnista, tämä lähde itsessään ei tallenna mitään tietoja. Täytyy vain kirjoittaa tapahtuman käsittelijä OnGetChartDataItem
jotta voidaan siirtää tietoja. Tätä tarkoitusta varten, tapahtuma ottaa var
parametrin AItem
joka on tyyppiä TChartDataItem
, joka on määritelty seuraavasti (elementtejä joita ei tarvita tässä on jätetty pois):
type
TChartDataItem = object
X, Y: Double;
// ...
end;
Kentät X
ja Y
ovat datapisteen koordinaatit.
Nyt kirjoitetaan tapahtuman käsittelijä OnGetChartDataItem
:
procedure TForm1.UserDefinedChartSource1GetChartDataItem(
ASource: TUserDefinedChartSource; AIndex: Integer; var AItem: TChartDataItem);
begin
AItem.X := PopulationData[AIndex].Age;
case Combobox1.ItemIndex of
0: AItem.Y := PopulationData[AIndex].Total;
1: AItem.Y := PopulationData[AIndex].Male;
2: AItem.Y := PopulationData[AIndex].Female;
3: AItem.Y := PopulationData[AIndex].Ratio;
end;
end;
AIndex
on indeksi tietokohtaan joka kysytään. Koska sekä kaavion lähde ja PopulationData
-taulukko alkaa indeksistä 0 niin vain luetaan data taulukkoa tällä indeksillä ja
vastaavaan elementin data kopioidaan AItem
:n . Huomaa, että poimimme y-koordinaatin sen mukaan, mitä on valittu yhdistelmäruutuun (ComboBox). Tällä tavoin UserDefinedChartSource voi tarjota monenlaisia tietoja.
On vielä joitakin tärkeitä asioita tekemättä:
- Asettaa että kuvaaja käyttää UserDefinedChartSource:a (sisäänrakennetun lähteen sijaan). Tätä varten laitetaan kuvaajan ominaisuus
Source
osoittamaanUserDefinedChartSource1
. - Kertoa UserDefinedChartSource:lle kuinka monta datapistettä ulkoisten tietojen taulukko sisältää. Datamäärä syötetään UserDefinedChartSource:n ominaisuuteen
PointsNumber
. Datamäärää tunnetaan sen jälkeen kun procedureLoadPopulationData
on päättynyt. Niinpä lisätään rivi, joka asettaaPointsNumber
oikeinFormCreate
metodiin sen jälkeen kun aliohjelmaaLoadPopulationData
on kutsuttu. - Koska UserDefinedChartSource ei tiedä mitään rakennetta ulkoisista tiedoista niin täytyy ehdottomasti ilmoittaa aina, kun tiedot ovat käytettävissä tai on muutettu. Teemme tämän kutsumalla
UserDefinedChartSource1.Reset
:ä sopivasta paikasta. Joskus olet onnekas että joku muu tapa saattanut tehdä tämän jo. Mutta pitää muistaa, että aina kun kaavio, jossa on UserDefinedChartSource ja se ei käyttäydy odotetulla tavalla on olemassa mahdollisuus, että olet ehkä unohtanut kutsuaReset
. Testaa, mitä tapahtuu jos "unohtaa"Reset
kutsun.
Hienosäätö
Käännä projekti tässä vaiheessa. Ei aivan huono, joitakin puutteita, vaikka.
- Saattaa olla että y-akselin merkinnnät ovat outoja. Miksi he hypätä 80000000, 1 ja sitten 12? Tämä johtuu virheestä joka oli jossain vanhoissa FPC:n versioissa jossa
Format
toiminto ei toiminut oikein suurilla numeroilla. Jos sinulla tämä virhe, vaihda omaisuuttaLeftAxis.Marks.Format
. Tämä merkkijono välitetäänFormat
funktiolle joka muuntaa numerot merkkijonoksi. Format tarkenteella "% .0n" vältetään muunnosvirhe ja lisäksi se lisää mukavan tuhaterottimen merkintöihin mikä tekee niistä paljon luettavaa. Huomaa kuitenkin, että "0" katkaisee desimaalit. Siksi tätä format tarkennetta kannattaa käyttää vain suurilla numeroilla. - On pieni väli y-akselin ja täytetty alueen kuvaajan välissä. Samoin on täytetty alue ulottuu vähän x-akselin alle. Tämä johtuu kaavion
Margins
ominaisuudesta. Tämä ominaisuus on yleensä varsin hyödyllistä luoda tyhjän alueella lähellä piste akselilla, vapaa päällekkäisiä tunnuksista ja merkinnöistä. Mutta nyt se on epäedullinen. Joten asettaaChart.Margins.Left
jaChart.Margins.Bottom
arvoon 0. - Ah niin yhdistelmäruutu (ComboBox) ei toimi vielä. Sille tarvitsee antaa toiminta sen
OnSelect
tapahtumaan. Mitä sillä valitaan? No, kun toinen ComboBox kohde valitaan niin UserDefinedChartSource raportoi sen y-arvon kuvaajalle - sitähän halutaan. Mutta miten saammeOnGetChartDataItem
kutsuttua? Vain kaavion uudelleen piirtämistä! Kun kuvaaja piirtyy uudelleen se saa aina kyselyn tiedot kaaviosta lähteestä, joten sillä on päivitetyt tiedot automaattisesti. OK - kutsukaammeChart1.Invalidate
:
procedure TForm1.ComboBox1Select(Sender: TObject);
begin
Chart1.Invalidate;
end;
Kun käännetään ja ohjelman ajossa valitaan toinen ComboBox kohde niin kuvaaja hienosti päivittyy valittuun luokkaan. Mutta akseli on jäätynyt, se ei muutu automaattisesti. Onko tämä virhe?
Ei. Muutama rivi aikaisemmin, sanottiin, että kun kaavio, jossa on UserDefinedChartSource ei toimi odotetulla tavalla on olemassa mahdollisuus, että olet ehkä unohtanut kutsua UserDefinedChartSource.Reset
. Siinä se on: korvataan Invalidate
Reset
-kutsulla .
Ja voimme korjata myös toisen puutteen: kuvateksti y-akselin pitäisi muuttua, kun toinen luokka on valittu. Tämä onnistuu kopioimalla yhdistelmäruudusta (ComboBox) valittu teksti kaavion LeftAxis.Title.Caption
. Lisätään tämä rivi yhdistelmäruudun (ComboBox) OnSelect tapahtumakäsittelijään:
procedure TForm1.ComboBox1Select(Sender: TObject);
begin
Chart1.LeftAxis.Title.Caption := Combobox1.Items[Combobox1.ItemIndex];
UserDefinedChartSource1.Reset;
end;
Nyt dataluokkien vaihto yhdistelmäruudulla (ComboBox) toimii hyvin.
Huomaa vasen alakulma: Koska y arvot eivät sisällä nollaa y-akselilla niin sen takia automaattinen akselin skaalaus ei ala nollasta. Mutta olisi mukavaa, jos siellä oli nolla. Tätä varten meidän täytyy osittain poistaa automaattinen akselin skaalaus. Chart.Extent
määrittelee minimi ja maksimi x- ja y-arvot suorakulmiota varten . Kun asetamme Chart.Extent.UseYMin
arvoon true
, y-akselin minimiksi on otettu arvo Chart.Extent.YMin
ja tämä on nolla oletuksena. Näin saadaan nolla takaisin.
Mutta on toinenkin pieni ongelma nyt: alin ruudukkoviiva piirretään x-akselin päälle. Ensinnäkin, mustat viivat, joita ehkä pidetään akseleina eivät ole akseleita, mutta kuuluvat kehyksen ympärille, ne piirretään kuvaaja alueella oletuksena. Voit testata tämän asettamalla Chart1.Frame.Visible
väliaikaisesti arvoon false
. Akseleilla on ominaisuus AxisPen
jossa "true" kytkee akselin viivan päälle AxisPen.Visible = true
. Sen jälkeen kun tekee tämän, x-akseli on edelleen katkoviivalla. Tämä johtuu piirustussekvenssistä. Oletuksena y-akseli, yhdessä sen ruudukko, piirretään x-akselin jälkeen. Jos halutaan saada niin että x-akselin piirretään ensin niin mene objektipuuhun ja vedä x-akseli y-akselin edelle.
Kun tämän jälkeen käännät ja ajat kaavion niin se on täydellinen.
Lähdekoodi
Project file
program project1;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Interfaces, // this includes the LCL widgetset
Forms, Unit1, tachartlazaruspkg, population
{ you can add units after this };
{$R *.res}
begin
RequireDerivedFormResource := True;
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
population.pas
unit population;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils;
type
TPopulationRecord = record
Age: Integer;
Total: Double;
Male: Double;
Female: Double;
Ratio: Double;
end;
type
TPopulationArray = array of TPopulationRecord;
procedure LoadPopulationData(const AFileName: String; var Data: TPopulationArray);
implementation
procedure LoadPopulationData(const AFileName: String; var Data: TPopulationArray);
function StripThousandSep(const s: String): String;
var
i: Integer;
begin
Result := s;
for i:=Length(Result) downto 1 do
if Result[i] = ',' then
Delete(Result, i, 1);
end;
var
List1, List2: TStringList;
i, j, n: Integer;
s: String;
ds: char;
begin
ds := FormatSettings.DecimalSeparator;
List1 := TStringList.Create;
try
List1.LoadFromFile(AFileName);
n := List1.Count;
SetLength(Data, n-2);
FormatSettings.DecimalSeparator := '.';
List2 := TStringList.Create;
try
List2.Delimiter := #9;
List2.StrictDelimiter := true;
j := 0;
for i:=2 to n-1 do begin
List2.DelimitedText := List1[i];
s := List1[i];
with Data[j] do begin
if i < n-1 then
Age := StrToInt(trim(List2[0]))
else
Age := 100;
Total := StrToFloat(StripThousandSep(trim(List2[1])));
Male := StrToFloat(StripThousandSep(trim(List2[2])));
Female := StrToFloat(StripThousandSep(trim(List2[3])));
Ratio := StrToFloat(trim(List2[4]));
end;
inc(j);
end;
finally
List2.Free;
end;
finally
FormatSettings.DecimalSeparator := ds;
List1.Free;
end;
end;
end.
Unit1.pas
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, TAGraph, TASources, TASeries, Forms, Controls,
Graphics, Dialogs, ExtCtrls, StdCtrls, population, TACustomSource;
type
{ TForm1 }
TForm1 = class(TForm)
Chart1: TChart;
Chart1AreaSeries1: TAreaSeries;
ComboBox1: TComboBox;
Panel1: TPanel;
UserDefinedChartSource1: TUserDefinedChartSource;
procedure ComboBox1Select(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure UserDefinedChartSource1GetChartDataItem(
ASource: TUserDefinedChartSource; AIndex: Integer;
var AItem: TChartDataItem);
private
{ private declarations }
PopulationData: TPopulationArray;
public
{ public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.lfm}
const
POPULATION_FILE = 'population.txt';
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
LoadPopulationData(POPULATION_FILE, PopulationData);
UserDefinedChartSource1.PointsNumber := Length(PopulationData);
Chart1.LeftAxis.Title.Caption := Combobox1.Items[Combobox1.ItemIndex];
end;
procedure TForm1.ComboBox1Select(Sender: TObject);
begin
Chart1.LeftAxis.Title.Caption := Combobox1.Items[Combobox1.ItemIndex];
UserDefinedChartSource1.Reset;
end;
procedure TForm1.UserDefinedChartSource1GetChartDataItem(
ASource: TUserDefinedChartSource; AIndex: Integer; var AItem: TChartDataItem);
begin
AItem.X := PopulationData[AIndex].Age;
case Combobox1.ItemIndex of
0: AItem.Y := PopulationData[AIndex].Total;
1: AItem.Y := PopulationData[AIndex].Male;
2: AItem.Y := PopulationData[AIndex].Female;
3: AItem.Y := PopulationData[AIndex].Ratio;
end;
end;
end.
Unit1.lfm
object Form1: TForm1
Left = 342
Height = 360
Top = 128
Width = 480
Caption = 'World population'
ClientHeight = 360
ClientWidth = 480
OnCreate = FormCreate
Position = poScreenCenter
LCLVersion = '1.1'
object Panel1: TPanel
Left = 0
Height = 36
Top = 0
Width = 480
Align = alTop
ClientHeight = 36
ClientWidth = 480
TabOrder = 0
object ComboBox1: TComboBox
Left = 12
Height = 23
Top = 5
Width = 196
ItemHeight = 15
ItemIndex = 0
Items.Strings = (
'Total population'
'Male population'
'Female population'
'Ratio male/female (%)'
)
OnSelect = ComboBox1Select
Style = csDropDownList
TabOrder = 0
Text = 'Total population'
end
end
object Chart1: TChart
Left = 0
Height = 324
Top = 36
Width = 480
AxisList = <
item
Grid.Visible = False
Alignment = calRight
AxisPen.Visible = True
Marks.Visible = False
Minors = <>
end
item
Grid.Color = clSilver
Alignment = calBottom
AxisPen.Visible = True
Minors = <>
Title.LabelFont.Style = [fsBold]
Title.Visible = True
Title.Caption = 'Age'
end
item
Grid.Color = clSilver
AxisPen.Visible = True
Marks.Format = '%.0n'
Marks.Style = smsCustom
Minors = <>
Title.LabelFont.Orientation = 900
Title.LabelFont.Style = [fsBold]
Title.Visible = True
end>
BackColor = clWhite
Extent.UseYMin = True
Foot.Alignment = taLeftJustify
Foot.Brush.Color = clBtnFace
Foot.Font.Color = clBlue
Foot.Text.Strings = (
'Source:'
'http://www.census.gov/population/international/data/worldpop/tool_population.php'
)
Foot.Visible = True
Margins.Left = 0
Margins.Right = 0
Margins.Bottom = 0
Title.Brush.Color = clBtnFace
Title.Font.Color = clBlue
Title.Font.Style = [fsBold]
Title.Text.Strings = (
'World population'
)
Title.Visible = True
Align = alClient
ParentColor = False
object Chart1AreaSeries1: TAreaSeries
AreaBrush.Color = clSkyBlue
AreaLinesPen.Style = psClear
Source = UserDefinedChartSource1
end
end
object UserDefinedChartSource1: TUserDefinedChartSource
OnGetChartDataItem = UserDefinedChartSource1GetChartDataItem
left = 552
top = 152
end
end