TAChart Tutorial: Dual y axis, Legend/fi: what changed
Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.
| Old | New | Line | |
|---|---|---|---|
| 1 | 1 | ## Johdanto | |
| 2 | 2 | ||
| 3 | 3 |  | |
| 4 | 4 | ||
| 5 | 5 | Kun erilaisia suureita piirretään samaan kaavioon niin tapahtuu melko usein, että niiden suurusluokat ovat eri alueella. "Tavallisessa" kaaviossa kuvaajia hallitsee suuret arvot pienten arvojen kustannuksella jotka "puristuu" tasaiseksi viivaksi. Kaaviolla olisi paljon enemmän merkitystä, jos erilaisia akseleita voidaan käyttää, yhdellä olisi isoja arvoja ja toisella pieniä arvoja. | |
| 6 | 6 | ||
| 7 | 7 | Kun olet käynyt läpi opetusohjelman [[TAChart Tutorial: Userdefined ChartSource/fi|käyttäjän määrittämä chartsource]] olet törmännyt tällaiseen tapaukseen. Siinä opetusohjelmassa tehtiin kaavio maailman väestöstä iän funktiona. Siinä on myös mahdollisuus nähdä miesten ja naisten määrien välinen suhde. Tämä jälkimmäinen luku on huomattavasti pienempi kuin väestön määrä. Joten, jos molemmat tiedot yhdistettäisiin samaan kaavion niin väestö suhde olisi kutistunut vaakasuoraksi viivaksi | |
| 8 | 8 | ||
| 9 | 9 | Tämä on taustana tähän projektiin. Katsotaan väestön tiedot uudelleen, piirretään väkiluku ja mies-nais suhde samaan kaavioon. | |
| 10 | 10 | ||
| 11 | 11 | Opit miten | |
| 12 | 12 | ||
| 13 | 13 | - luoda kaavion, jossa on **kaksi y-akselia** | |
| 14 | 14 | - käyttää automaattista skaalausta akselin asteikko muunnoksessa | |
| 15 | 15 | - tehdään käyttäjän muokkaamia akselin merkintöjä | |
| 16 | 16 | - tehdään kuvaajien otsikkopalkki (legend) käyttämällä vähemmän tunnettuja ominaisuuksia. | |
| 17 | 17 | ||
| 18 | 18 | Kuten tavallista niin sinun tulisi tuntea Lazarus ja FPC sekä pitäisi olla perustiedot TAChart kirjastosta (käy ensin läpi [[TAChart Tutorial: Getting started/fi|TAChart:n aloitus opetusohjelma]] ). Tässä nimenomaisessa opetusohjelmassa olisi myös hyödyllistä, jos olisit tutkinut opetusohjelman [[TAChart Tutorial: Userdefined ChartSource/fi|käyttäjän määrittämiä chartsource]] ensin. | |
| 19 | 19 | ||
| 20 | 20 | ## Data | |
| 21 | 21 | ||
| 22 | 22 | Kuten jo mainittiin käytämme samoja tietoja kuin [[TAChart Tutorial: Userdefined ChartSource/fi|käyttäjän määrittämä chartsource]] opetusohjelmassa. Ensisijainen datatiedosto on nimeltään "`population.txt`" ja peräisin [www.census.gov](http://www.census.gov/population/international/data/worldpop/tool_population.php). Kopioi tämä tiedosto [[TAChart Tutorial: Userdefined ChartSource/fi|käyttäjän määrittämä chartsource]] opetusohjelma projektin hakemistosta tai seuraa siellä olevia ohjeita miten ladata tiedosto. Käännösyksikkö population.pas lukee tämän tiedoston ja tallentaa tiedot tietueiden `TPopulationRecord`taulukkoon | |
| 23 | 23 | ||
| 24 | 24 | ``` pascal | |
| 25 | 25 | type | |
| 26 | 26 | TPopulationRecord = record | |
| 27 | 27 | Age: Integer; | |
| 28 | 28 | Total: Double; | |
| 29 | 29 | Male: Double; | |
| 30 | 30 | Female: Double; | |
| 31 | 31 | Ratio: Double; | |
| 32 | 32 | end; | |
| 33 | 33 | ||
| 34 | 34 | TPopulationArray = array of TPopulationRecord; | |
| 35 | 35 | ``` | |
| 36 | 36 | ||
| 37 | 37 | Nyt on kaikki mitä tarvitaan jotta voidaan aloittaa uuden projektin. Lisää käännösyksikkö population.pas lomakkeen uses luetteloon. Lisää muuttujan `PopulationData` tyyppiä `TPopulationArray` esittely lomakkeen `private` osaan. Ja datatiedoston lukemisen kutsu `LoadPopulationData` (joka on population unit käännösyksikössä) lomakkeen `OnCreate` tapahtumaan: | |
| 38 | 38 | ||
| 39 | 39 | ``` pascal | |
| 40 | 40 | uses | |
| 41 | 41 | ..., population, ...; | |
| 42 | 42 | ||
| 43 | 43 | type | |
| 44 | 44 | TForm1 = class(TForm) | |
| 45 | 45 | // ... | |
| 46 | 46 | private | |
| 47 | 47 | PopulationData : TPopulationArray; | |
| 48 | 48 | // ... | |
| 49 | 49 | end; | |
| 50 | 50 | ||
| 51 | 51 | const | |
| 52 | 52 | POPULATION_FILE = 'population.txt'; | |
| 53 | 53 | ||
| 54 | 54 | procedure TForm1.FormCreate(Sender: TObject); | |
| 55 | 55 | begin | |
| 56 | 56 | LoadPopulationData(POPULATION_FILE, PopulationData); | |
| 57 | 57 | end; | |
| 58 | 58 | ``` | |
| 59 | 59 | ||
| 60 | 60 | ## Kaavion valmistelu | |
| 61 | 61 | ||
| 62 | 62 | Nyt voidaan aloittaa kaavion valmistelu... | |
| 63 | 63 | ||
| 64 | 64 | Lisää **`TChart`** komponentti lomakkeelle: | |
| 65 | 65 | ||
| 66 | 66 | - Aseta sen Align ominaisuus arvoon `alClient`. | |
| 67 | 67 | - Aseta sen `BackColor` arvoon `clWhite`. | |
| 68 | 68 | ||
| 69 | 69 | Lisää **kolme viivakuvaajaa** kaavioon: | |
| 70 | 70 | ||
| 71 | 71 | - Vaihda niiden nimet kuvailemmiksi `LineSeries_male`, `LineSeries_female` ja `LineSeries_ratio`. | |
| 72 | 72 | - Aseta `SeriesColor` väri kuvaajalla `LineSeries_male` arvoon `clSkyBlue`, kuvaajalla `LineSeries_female` arvoon `$00FF80FF` ja jätä `LineSeries_ratio` kuvaajan väri mustaksi. | |
| 73 | 73 | ||
| 74 | 74 | Mistä kuvaajat saavat tietonsa? Tiedot on tallennettu `PopulationData` taulukkoon, joten se olisi parasta hyödyntää [[TAChart documentation|käyttäjän määrittämä kaavio lähde]] . Tarkasti ottaen: tarvitaan kolme kaavion lähdettä, yksi kutakin kuvaajaa kohti. | |
| 75 | 75 | ||
| 76 | 76 | Siksi lisää kolme `TUserDefinedChartSource` -komponenttia lomakkeelle: | |
| 77 | 77 | ||
| 78 | 78 | - Nimetään ne kuvaammaksi `ChartSource_male`, `ChartSource_female` ja `ChartSource_ratio`. | |
| 79 | 79 | - Aseta kunkin chartsource ominaisuus `Source` vastaamaan oikeata viivakuvaajaa. | |
| 80 | 80 | - Kirjoitetaan seuraava tapahtumankäsittelijä ja liitetään se kunkin user-defined chart source:n `OnGetDataItem` tapahtumaan: | |
| 81 | 81 | ||
| 82 | 82 | ``` pascal | |
| 83 | 83 | procedure TForm1.ChartSourceGetChartDataItem( | |
| 84 | 84 | ASource: TUserDefinedChartSource; AIndex: Integer; var AItem: TChartDataItem); | |
| 85 | 85 | begin | |
| 86 | 86 | AItem.X := PopulationData[AIndex].Age; | |
| 87 | 87 | if ASource = ChartSource_male then | |
| 88 | 88 | AItem.Y := PopulationData[AIndex].Male / 1e6 | |
| 89 | 89 | else if ASource = ChartSource_female then | |
| 90 | 90 | AItem.Y := PopulationData[AIndex].Female / 1e6 | |
| 91 | 91 | else | |
| 92 | 92 | AItem.Y := PopulationData[AIndex].Ratio / 100; | |
| 93 | 93 | end; | |
| 94 | 94 | ``` | |
| 95 | 95 | ||
| 96 | 96 | Seuraavaksi kerrotaan kaavion datan lähde, josta ottaa datan: x: n arvo on otettu tietueen `TPopulationRecord` kentästä `Age`, ja riippuen kaavion datalähteestä, y:n arvo otetaan tietueen `TPopulationRecord` kentistä `Male`, `Female` tai `Ratio`. | |
| 97 | 97 | ||
| 98 | 98 | Väkiluku jaetaan miljoonalla jolloin päästään eroon monista nollista. Joten pitää muistaa kertoa että väestöakselilla luvut ovat miljoonia. Myös mies-naissuhde jaetaan 100:lla koska käytetään vain jakojäännöksiä, ei prosentteja. | |
| 99 | 99 | ||
| 100 | 100 | Onko jo aika kääntää? Ei, ei vielä. Vielä on kerrottava kaavion lähteissä datapisteiden lukumäärä. Tämä tieto saadaan tiedoston lukemisen jälkeen. Aseteaan ominaisuus `PointNumber` kunkin kaavion datalähteen taulukon pituuden mukaiseksi lomakkeen `OnCreate` tapahtumakäsittelyssä: | |
| 101 | 101 | ||
| 102 | 102 | ``` pascal | |
| 103 | 103 | procedure TForm1.FormCreate(Sender: TObject); | |
| 104 | 104 | begin | |
| 105 | 105 | LoadPopulationData(POPULATION_FILE, PopulationData); | |
| 106 | 106 | ChartSource_male.PointsNumber := Length(PopulationData); | |
| 107 | 107 | ChartSource_female.PointsNumber := Length(PopulationData); | |
| 108 | 108 | ChartSource_ratio.PointsNumber := Length(PopulationData); | |
| 109 | 109 | end; | |
| 110 | 110 | ``` | |
| 111 | 111 | ||
| 112 | 112 | Tässä vaiheessa lomake ja objekti puu pitäisi näyttää tältä: | |
| 113 | 113 | ||
| 114 | 114 |   | |
| 115 | 115 | ||
| 116 | 116 | ja kun tuon kääntää ja ajaa niin tuloksen pitäisi näyttää joltain tämäntapaiselta: | |
| 117 | 117 | ||
| 118 | 118 |  | |
| 119 | 119 | ||
| 120 | 120 | Kuten johdannossa mainittiin, mies-nais suhde - joka on musta viiva - on erittäin tasainen. Tämä johtuu arvojen erisuuruidesta: väestön määrä menee melkein nollasta 70:n, kun suhde on lähes aina noin yksi. | |
| 121 | 121 | ||
| 122 | 122 | ## Asetetaan toinen y-akseli | |
| 123 | 123 | ||
| 124 | 124 | Tarvitaan toinen y-akseli. | |
| 125 | 125 | ||
| 126 | 126 | Tätä varten siirry objektipuuhun, klikkaa hiiren oikealla `AxisList` (oikealla `Chart1`:n alla ) ja lisää uusi akseli kaavioon. Nyt meillä on kolme akselia: | |
| 127 | 127 | ||
| 128 | 128 |  | |
| 129 | 129 | ||
| 130 | 130 | Valitse akseli \#2, joka on vielä vasen akseli tällä hetkellä ja aseta komponenttimuokkaimessa sen `Alignment` arvoon `calRight`. | |
| 131 | 131 | ||
| 132 | 132 | Tämä on ehkä hyvä paikka mainita, että kaksi y-akselia ei ole maksimi. Akseleiden määrää voi lisätä kaavion `AxisList` kohtaan. Niitä voidaan käyttää pystyakseleina vasemmalle tai oikealle tai vaaka-akseleina kaavion yläreunassa tai alareunassa. Tutustu akselin parametriin [[TAChart documentation|Group]]: saman ryhmän akselit piirretään samaan suorakulmion, akselien eri ryhmän arvot on piirretty side-by-side. | |
| 133 | 133 | ||
| 134 | 134 | Miten kaavio tietää, mikä kuvaaja kuuluu mihinkin akseliin? Tätä tarkoitusta varten kullakin kuvaajalla on ominaisuuksia `AxisIndexX` ja `AxisIndexY`. Koska miesten ja naisten kuvaajat piirretään vasemman akselin mukaan asetetaan `AxisIndexY` arvoon 0 - katsomalla objektipuusta nähdään, että tämä indeksi kuuluu vasemmalle akselille. Suhde kuvaaja kuuluu oikealle akselille, sen `AxisIndexY` on oltava 2. Voisimme myös asettaa akselin indeksin ala-akselille, mutta tämä ei ole välttämätöntä tässä. | |
| 135 | 135 | ||
| 136 | 136 | Kun tämä käännetään niin tulos on hieman pettymys: ei muutosta - musta suhde-kuvaaja on edelleen hyvin tasainen. | |
| 137 | 137 | ||
| 138 | 138 |  | |
| 139 | 139 | ||
| 140 | 140 | ## Akselin asettaminen automaattisesti muuttuvaan mittakaavaan | |
| 141 | 141 | ||
| 142 | 142 | Syy siihen, miksi toinen y-akseli ei skaalaudu riippumattomasti ensimmäiseen on se, että ohittaa sisäisten koordinaattijärjestelmän. TAChart käyttää kolmea koordinaattijärjestelmää siirrettäessä "reaalimaailman" dataa pikseleihin ruudulle: | |
| 143 | 143 | ||
| 144 | 144 | - [[TAChart documentation|akselin koordinaatit]] ovat koordinaatit, jossa tiedot tulevat tai toisin sanoen, ne jotka on merkitty akselille. Esimerkiksi tässä projektissa nämä ovat väestön määrä (miljoonalla jaon jälkeen), eli numerot välillä 0 - 70. | |
| 145 | 145 | - [[TAChart documentation|kaavio koordinaatit]] saadaan sovittamisen jälkeen tehdyistä muutoksista. On olemassa toinen opetusohjelma, jossa käytetään logaritminen muunnosta, tässä tapauksessa kuvaajan koordinaatit olisivat lähtötietoja logaritmille. | |
| 146 | 146 | - [[TAChart documentation|kuva koordinaatit]] kuuluvat pikseliä ruudulla lasketaan kuvaajan koordinaatteja. | |
| 147 | 147 | ||
| 148 | 148 | Niin, on olemassa kaksi muunnostoimintoa mukana laskettaessa koordinaattien datapisteet näytölle: | |
| 149 | 149 | ||
| 150 | 150 | 1. akselin koordinaatit näytön koordinaatteihin käyttäjän määrittelemän `TChartAxisTransform` avulla | |
| 151 | 151 | 2. näytön koordinaatit kuvan koordinaateiksi yksinkertaisella lineaarikuvauksen avulla | |
| 152 | 152 | ||
| 153 | 153 | Jos akselin siirtymistä akselista kaavion koordinaatteihin ei ole määritelty niin käytetään yksinkertaista 1: 1 muunnosta. | |
| 154 | 154 | ||
| 155 | 155 | Ai niin - käytössä ei ole mitään akselin muunnosta. Siksi *väestö ja*mies/nais-suhde'' tiedot ovat edelleen yhteisen maailman koordinaateissa. Jos halutaan erottaa kaksi tietokokonaisuutta meidän soveltaa muunnosta jokaiselle tietokokonaisuudelle, joka kuvaa sitä samaa aluetta kuvaajan koordinaattien vaikkapa 0-1. | |
| 156 | 156 | ||
| 157 | 157 | Tätä tarkoitusta varten TAChart on [[TAChart documentation|TAutoScaleAxisTransform]]. Siihen ei ole suoraa pääsyä komponenttipaletilta, mutta sen lapsi löytyy: [[TAChart documentation|TChartAxisTransformations]] -komponentti. | |
| 158 | 158 | ||
| 159 | 159 | Joten, vedä ja pudota kaksi `TChartAxisTransformations` komponenttia lomakkeelle, yksi vasemmalle akselille, yksi oikealle akselille. Nimeä ne `LeftAxisTransformations` ja `RightAxisTransformations` , vastaavasti. Kaksoisnapsauta kutakin näistä komponenteista ja valitse *"Automaattinen skaalaus"* (*"auto scale"*) *Muokkaa akselimuunnoksia* (*axis transformations*)-editorista. Objektipuusta, näet kunkin `ChartAxisTransformations` komponentin lapsen. Nimeä nämä lapset `LeftAxisAutoscaleTransform` ja `RightAxisAutoscaleTransform` , vastaavasti. Aseta `LeftAxisTransformations` ja `RightAxisTransformations` vastaavasti akselien Left (vasemman) ja Right (oikea) ominaisuuteen `Transformations` . | |
| 160 | 160 | ||
| 161 | 161 | Tältä näyttää objektipuu näiden käsittelyiden jälkeen: | |
| 162 | 162 | ||
| 163 | 163 |  | |
| 164 | 164 | ||
| 165 | 165 | Katso `AutoScaleAxisTransforms` :n ominaisuudet `MaxValue` ja `MinValue`. Molemmilla muunnokset ovat samat `MaxValue = 1` ja `MinValue = 0`. Tämä tarkoittaa, että kukin akseli on sijoitettu välille 0 ja 1, toisin sanoen, molempia aineistoja puristetaan tai laajennetaan täyttämään kaavion alue kokonaan. | |
| 166 | 166 | ||
| 167 | 167 | Kun käännetään ja ajetaan ohjelma niin silloin nähdään mitä tarkalleen tapahtuu. | |
| 168 | 168 | ||
| 169 | 169 |  | |
| 170 | 170 | ||
| 171 | 171 | Voisimme myös esimerkiksi asettaa `RightAxisAutoscaleTransform` ominaisuudet `MinValue=1` ja `MaxValue=2` . Sitten väestötiedot olisi edelleen kartoitettu akselille 0-1, ja mies-nais suhde data olisi kartoitettu alueelle 1-2, eli meillä olisi paned kaavio, jossa alempi puolisko näyttäisi *väestön* kuvaajat ja ylempi puoli *suhde* kuvaajan. Mutta se kuuluu toiseen opetusohjelmaan. Jätämme `MinValue` ja `MaxValue` ominaisuudet oletusarvoihin. | |
| 172 | 172 | ||
| 173 | 173 | ## Hieno viilaus | |
| 174 | 174 | ||
| 175 | 175 | Vaikein osa on tehty nyt. Jäljellä on vielä kaavion "heino viilaus". | |
| 176 | 176 | ||
| 177 | 177 | - Poista akseliruudukko (tämä on jo aikaisemmin opetettu). Vasemman ja oikeamman akselin viivat tekevät siitä hyvin sekavan | |
| 178 | 178 | - Lisää **akseleiden otsikot**: | |
| 179 | 179 | - *"Väestö"* tai *"Population"* vasemmalle akselille | |
| 180 | 180 | - *"Mies/Nais suhde"* tai *"Male-to-female ratio"* oikealle akselille | |
| 181 | 181 | - *"Ikä (vuosina)"* tai *"Age (Years)"* ala akselille. | |
| 182 | 182 | - Aseta akselien `LabelFont.Style` arvoon `fsBold`. | |
| 183 | 183 | - Huomaa, että oikean akselin otsikkoa ei kierretty. Akselin `Title.LabelFont` on ominaisuus `Orientation` - tämä kierton kulma ilmoitetaan kymmenesosa asteina. Aseta se arvoon 900 niin saadaan sama suunta kuin vasemman akselin otsikolla. | |
| 184 | 184 | - Pakota oikea akseli **aloittamaan nollasta** asettamalla sen Range.`Range.UseMin` arvoon `true`. Tämä on riittävä, koska `Range.XMin` on 0 oletuksena. Tällä tavalla voit myös valita muita alueita akselille. | |
| 185 | 185 | ||
| 186 | 186 | ### Muokataan akselin väliviivojen tekstiä | |
| 187 | 187 | ||
| 188 | 188 | Nyt pitäisi huolehtia siitä, että tulodata on jaettu 1E6 (eli miljoonalla), jotenkin tämä pitäisi osoittaa. Olisi mahdollista muuttaa vasemman akselin otsikkoa "Väestö (miljoonaa)". Mutta mennään eri tavalla nyt: liitetään "M" osoittamaan oikeaan suuruusluokkaa ( "M" = "Mega" = "miljoona") asteikkomerkkeihin. On kaksi tapaa saavuttaa näin: ensimmäinen, on käyttää akselin ominaisuutta `Marks.Format`, jossa "M" voidaan lisätä (% 0: .9g M '). Tai toisella tavoin voimme hyödyntää TChartAxis:n tapahtuman `OnMarkToText`; Tämä tapahtuma mahdollistaa muuttaa asteikkomerkkien tekstiä joka näytetään akselilla. Tässä tapauksessa tehdään seuraavanlainen aliohjelma vasemman akselin tapahtumaan : | |
| 189 | 189 | ||
| 190 | 190 | ``` pascal | |
| 191 | 191 | procedure TForm1.Chart1AxisList0MarkToText(var AText: String; AMark: Double); | |
| 192 | 192 | begin | |
| 193 | 193 | AText := Format('%s M', [AText]); | |
| 194 | 194 | end; | |
| 195 | 195 | ``` | |
| 196 | 196 | ||
| 197 | 197 |  | |
| 198 | 198 | ||
| 199 | 199 | ### Asetetaan kuvaajien otsikkopalkki (legend) | |
| 200 | 200 | ||
| 201 | 201 | Opetusohjelma on melkein valmis nyt. Mitä on jäljellä on [[TAChart documentation|kuvaajien otsikkopalkki (legend)]] - toistaiseksi ei kerrota käyttäjälle mikä käyrä kuuluu mihinkin tietoihin. Joten, mennään `Chart1.Legend` ominaisuuteen ja asetetaan `Visible` arvoon `true` . Nyt nähdään vain lyhyitä viivoja kuvaajien otsikkopalkkissa (legend), ei tekstejä. Tämä johtuu siitä, ettei ole määritelty `Title`:ä jokaiselle kuvaajalle. Tehdään tämä nyt, kirjoitetaan otsikot *"miehet"* (*"male"*), *"naiset"* (*"female"*) ja *"suhde"*(*"ratio"*) vastaaviin kuvaajiin. | |
| 202 | 202 | ||
| 203 | 203 |  | |
| 204 | 204 | ||
| 205 | 205 | Koska on kaksi akselia, olisi hienoa saada otsikko "vasen" (*"left"* )miesten ja naisten kuuvaajien yläpuolelle kuvaajien otsikkopalkkiin ja otsikko "oikea" (*"right"*) suhteen kuvaajan yläpuolella. Tämä vaikutus voidaan saavuttaa käyttämällä ryhmiä. Jokaisella kuvaajalla on ominaisuus `Legend` , jota voidaan käyttää ohjaamaan paikkaa kuvaajien otsikkopalkkiin . Ominaisuus `GroupIndex` mahdollistaa kuvaajien ryhmien saamisen yhteisen otsikon alle. Joten, aseta `Legend.GroupIndex` miesten ja naisten kuvaajat nollaan (0) ja aseta suhteen kuvaaja arvoon yksi (1). Sitten mennään `Chart.Legend` ja määrittellään ryhmän otsikot kirjoittamalla tekstiä omaisuutta `Chart.Legend.GroupTitles` ominaisuuteen. Kukin rivi vastaa aina kutakin `GroupIndex` arvoa. Voidaan myös mennä `Chart.Legend.GroupFont` ominaisuuteen ja asettaa fontin lihavoiduksi. | |
| 206 | 206 | ||
| 207 | 207 | Vielä parannettavaa: entäpä jos kuvaajien otsikkopalkki olisi kaavion alla niin, että kuvaajat jotka kuuluvat vasemmalle akselille olisivat vasemmalla ja ne jotka kuuluvat oikealle akselille olisivat oikealla? | |
| 208 | 208 | ||
| 209 | 209 | Aluksi asetamme kuvaajien otsikkopalkin `Alignment` arvoon `laBottomCenter`. Sitten muutamme `ColumnCount` arvoon 2. Olemme onnekkaita - kaikki kuvaajien otsikkopalkin merkinnät mahtuvat menevät aivan kuten haluamme. Muuten täytyisi muokata niiden [[TAChart documentation|Order]] ominaisuutta tai muuttaa omaisuutta `ItemFillOrder` tai ehkä ottaa käyttöön ([[TAChart documentation|tyhjiä kohteita]]). | |
| 210 | 210 | ||
| 211 | 211 | TAChart's kuvaajien otsikkopalkki (legend) on erittäin joustava -- Ole hyvä ja katso sen [[TAChart documentation|dokumentaatio]] saadaksesi lisätietoa siitä. | |
| 212 | 212 | ||
| 213 | 213 | Lopuksi, lisätään teksti *"World population"* kaavion otsikkoon ja referenssi tiedot alatunnisteeseen. | |
| 214 | 214 | ||
| 215 | 215 |  | |
| 216 | 216 | ||
| 217 | 217 | ## Lähdekoodi | |
| 218 | 218 | ||
| 219 | 219 | ### Project file | |
| 220 | 220 | ||
| 221 | 221 | ``` pascal | |
| 222 | 222 | program project1; | |
| 223 | 223 | ||
| 224 | 224 | {$mode objfpc}{$H+} | |
| 225 | 225 | ||
| 226 | 226 | uses | |
| 227 | 227 | {$IFDEF UNIX}{$IFDEF UseCThreads} | |
| 228 | 228 | cthreads, | |
| 229 | 229 | {$ENDIF}{$ENDIF} | |
| 230 | 230 | Interfaces, // this includes the LCL widgetset | |
| 231 | 231 | Forms, Unit1, tachartlazaruspkg | |
| 232 | 232 | { you can add units after this }; | |
| 233 | 233 | ||
| 234 | 234 | {$R *.res} | |
| 235 | 235 | ||
| 236 | 236 | begin | |
| 237 | 237 | RequireDerivedFormResource := True; | |
| 238 | 238 | Application.Initialize; | |
| 239 | 239 | Application.CreateForm(TForm1, Form1); | |
| 240 | 240 | Application.Run; | |
| 241 | 241 | end. | |
| 242 | 242 | ``` | |
| 243 | 243 | ||
| 244 | 244 | ### Unit1.pas | |
| 245 | 245 | ||
| 246 | 246 | ``` pascal | |
| 247 | 247 | unit Unit1; | |
| 248 | 248 | ||
| 249 | 249 | {$mode objfpc}{$H+} | |
| 250 | 250 | ||
| 251 | 251 | interface | |
| 252 | 252 | ||
| 253 | 253 | uses | |
| 254 | 254 | Classes, SysUtils, FileUtil, TAGraph, TASeries, TASources, Forms, Controls, | |
| 255 | 255 | Graphics, Dialogs, population, TACustomSource, TATransformations, TAChartAxisUtils; | |
| 256 | 256 | ||
| 257 | 257 | type | |
| 258 | 258 | ||
| 259 | 259 | { TForm1 } | |
| 260 | 260 | ||
| 261 | 261 | TForm1 = class(TForm) | |
| 262 | 262 | Chart1: TChart; | |
| 263 | 263 | LeftAxisTransformations: TChartAxisTransformations; | |
| 264 | 264 | LeftAxisAutoScaleTransform: TAutoScaleAxisTransform; | |
| 265 | 265 | RightAxisTransformations: TChartAxisTransformations; | |
| 266 | 266 | LineSeries_male: TLineSeries; | |
| 267 | 267 | LineSeries_female: TLineSeries; | |
| 268 | 268 | LineSeries_ratio: TLineSeries; | |
| 269 | 269 | ChartSource_male: TUserDefinedChartSource; | |
| 270 | 270 | ChartSource_female: TUserDefinedChartSource; | |
| 271 | 271 | ChartSource_ratio: TUserDefinedChartSource; | |
| 272 | 272 | RightAxisAutoScaleTransform: TAutoScaleAxisTransform; | |
| 273 | 273 | procedure Chart1AxisList0MarkToText(var AText: String; AMark: Double); | |
| 274 | 274 | procedure ChartSourceGetChartDataItem( | |
| 275 | 275 | ASource: TUserDefinedChartSource; AIndex: Integer; | |
| 276 | 276 | var AItem: TChartDataItem); | |
| 277 | 277 | procedure FormCreate(Sender: TObject); | |
| 278 | 278 | private | |
| 279 | 279 | { private declarations } | |
| 280 | 280 | PopulationData: TPopulationArray; | |
| 281 | 281 | public | |
| 282 | 282 | { public declarations } | |
| 283 | 283 | end; | |
| 284 | 284 | ||
| 285 | 285 | var | |
| 286 | 286 | Form1: TForm1; | |
| 287 | 287 | ||
| 288 | 288 | implementation | |
| 289 | 289 | ||
| 290 | 290 | {$R *.lfm} | |
| 291 | 291 | ||
| 292 | 292 | const | |
| 293 | 293 | POPULATION_FILE = 'population.txt'; | |
| 294 | 294 | ||
| 295 | 295 | { TForm1 } | |
| 296 | 296 | ||
| 297 | 297 | procedure TForm1.ChartSourceGetChartDataItem( | |
| 298 | 298 | ASource: TUserDefinedChartSource; AIndex: Integer; var AItem: TChartDataItem); | |
| 299 | 299 | begin | |
| 300 | 300 | AItem.X := PopulationData[AIndex].Age; | |
| 301 | 301 | if ASource = ChartSource_male then | |
| 302 | 302 | AItem.Y := PopulationData[AIndex].Male / 1e6 | |
| 303 | 303 | else if ASource = ChartSource_female then | |
| 304 | 304 | AItem.Y := PopulationData[AIndex].Female / 1e6 | |
| 305 | 305 | else | |
| 306 | 306 | AItem.Y := PopulationData[AIndex].Ratio / 100; | |
| 307 | 307 | end; | |
| 308 | 308 | ||
| 309 | 309 | procedure TForm1.Chart1AxisList0MarkToText(var AText: String; AMark: Double); | |
| 310 | 310 | begin | |
| 311 | 311 | AText := Format('%s M', [AText]); | |
| 312 | 312 | end; | |
| 313 | 313 | ||
| 314 | 314 | procedure TForm1.FormCreate(Sender: TObject); | |
| 315 | 315 | begin | |
| 316 | 316 | LoadPopulationData(POPULATION_FILE, PopulationData); | |
| 317 | 317 | ChartSource_male.PointsNumber := Length(PopulationData); | |
| 318 | 318 | ChartSource_female.PointsNumber := Length(PopulationData); | |
| 319 | 319 | ChartSource_ratio.PointsNumber := Length(PopulationData); | |
| 320 | 320 | end; | |
| 321 | 321 | ||
| 322 | 322 | end. | |
| 323 | 323 | ``` | |
| 324 | 324 | ||
| 325 | 325 | ### Unit1.lfm | |
| 326 | 326 | ||
| 327 | - | ||
| 327 | + | ``` lfm | |
| 328 | 328 | object Form1: TForm1 | |
| 329 | 329 | Left = 365 | |
| 330 | 330 | Height = 409 | |
| 331 | 331 | Top = 169 | |
| 332 | 332 | Width = 470 | |
| 333 | 333 | Caption = 'Form1' | |
| 334 | 334 | ClientHeight = 409 | |
| 335 | 335 | ClientWidth = 470 | |
| 336 | 336 | OnCreate = FormCreate | |
| 337 | 337 | LCLVersion = '1.1' | |
| 338 | 338 | object Chart1: TChart | |
| 339 | 339 | Left = 0 | |
| 340 | 340 | Height = 409 | |
| 341 | 341 | Top = 0 | |
| 342 | 342 | Width = 470 | |
| 343 | 343 | AxisList = < | |
| 344 | 344 | item | |
| 345 | 345 | Grid.Visible = False | |
| 346 | 346 | Minors = <> | |
| 347 | 347 | Title.LabelFont.Orientation = 900 | |
| 348 | 348 | Title.LabelFont.Style = [fsBold] | |
| 349 | 349 | Title.Visible = True | |
| 350 | 350 | Title.Caption = 'Population' | |
| 351 | 351 | Transformations = LeftAxisTransformations | |
| 352 | 352 | OnMarkToText = Chart1AxisList0MarkToText | |
| 353 | 353 | end | |
| 354 | 354 | item | |
| 355 | 355 | Grid.Visible = False | |
| 356 | 356 | Alignment = calBottom | |
| 357 | 357 | Minors = <> | |
| 358 | 358 | Title.LabelFont.Style = [fsBold] | |
| 359 | 359 | Title.Visible = True | |
| 360 | 360 | Title.Caption = 'Age (Years)' | |
| 361 | 361 | end | |
| 362 | 362 | item | |
| 363 | 363 | Grid.Visible = False | |
| 364 | 364 | Alignment = calRight | |
| 365 | 365 | Minors = <> | |
| 366 | 366 | Range.UseMin = True | |
| 367 | 367 | Title.LabelFont.Orientation = 900 | |
| 368 | 368 | Title.LabelFont.Style = [fsBold] | |
| 369 | 369 | Title.Visible = True | |
| 370 | 370 | Title.Caption = 'Male-to-female ratio' | |
| 371 | 371 | Transformations = RightAxisTransformations | |
| 372 | 372 | end> | |
| 373 | 373 | BackColor = clWhite | |
| 374 | 374 | Foot.Alignment = taLeftJustify | |
| 375 | 375 | Foot.Brush.Color = clBtnFace | |
| 376 | 376 | Foot.Font.Color = clBlue | |
| 377 | 377 | Foot.Text.Strings = ( | |
| 378 | 378 | 'Source:' | |
| 379 | 379 | 'http://www.census.gov/population/international/data/worldpop/tool_population.php' | |
| 380 | 380 | ) | |
| 381 | 381 | Foot.Visible = True | |
| 382 | 382 | Legend.Alignment = laBottomCenter | |
| 383 | 383 | Legend.ColumnCount = 2 | |
| 384 | 384 | Legend.GroupFont.Style = [fsBold] | |
| 385 | 385 | Legend.GroupTitles.Strings = ( | |
| 386 | 386 | 'left:' | |
| 387 | 387 | 'right:' | |
| 388 | 388 | ) | |
| 389 | 389 | Legend.Visible = True | |
| 390 | 390 | Title.Brush.Color = clBtnFace | |
| 391 | 391 | Title.Font.Color = clBlue | |
| 392 | 392 | Title.Font.Style = [fsBold] | |
| 393 | 393 | Title.Text.Strings = ( | |
| 394 | 394 | 'World population' | |
| 395 | 395 | ) | |
| 396 | 396 | Title.Visible = True | |
| 397 | 397 | Align = alClient | |
| 398 | 398 | ParentColor = False | |
| 399 | 399 | object LineSeries_male: TLineSeries | |
| 400 | 400 | Legend.GroupIndex = 0 | |
| 401 | 401 | Title = 'male' | |
| 402 | 402 | AxisIndexY = 0 | |
| 403 | 403 | LinePen.Color = clSkyBlue | |
| 404 | 404 | Source = ChartSource_male | |
| 405 | 405 | end | |
| 406 | 406 | object LineSeries_female: TLineSeries | |
| 407 | 407 | Legend.GroupIndex = 0 | |
| 408 | 408 | Title = 'female' | |
| 409 | 409 | AxisIndexY = 0 | |
| 410 | 410 | LinePen.Color = 16744703 | |
| 411 | 411 | Source = ChartSource_female | |
| 412 | 412 | end | |
| 413 | 413 | object LineSeries_ratio: TLineSeries | |
| 414 | 414 | Legend.GroupIndex = 1 | |
| 415 | 415 | Title = 'ratio' | |
| 416 | 416 | AxisIndexY = 2 | |
| 417 | 417 | Source = ChartSource_ratio | |
| 418 | 418 | end | |
| 419 | 419 | end | |
| 420 | 420 | object ChartSource_male: TUserDefinedChartSource | |
| 421 | 421 | OnGetChartDataItem = ChartSourceGetChartDataItem | |
| 422 | 422 | left = 130 | |
| 423 | 423 | top = 25 | |
| 424 | 424 | end | |
| 425 | 425 | object ChartSource_female: TUserDefinedChartSource | |
| 426 | 426 | OnGetChartDataItem = ChartSourceGetChartDataItem | |
| 427 | 427 | left = 130 | |
| 428 | 428 | top = 79 | |
| 429 | 429 | end | |
| 430 | 430 | object ChartSource_ratio: TUserDefinedChartSource | |
| 431 | 431 | OnGetChartDataItem = ChartSourceGetChartDataItem | |
| 432 | 432 | left = 130 | |
| 433 | 433 | top = 137 | |
| 434 | 434 | end | |
| 435 | 435 | object LeftAxisTransformations: TChartAxisTransformations | |
| 436 | 436 | left = 267 | |
| 437 | 437 | top = 25 | |
| 438 | 438 | object LeftAxisAutoScaleTransform: TAutoScaleAxisTransform | |
| 439 | 439 | end | |
| 440 | 440 | end | |
| 441 | 441 | object RightAxisTransformations: TChartAxisTransformations | |
| 442 | 442 | left = 267 | |
| 443 | 443 | top = 136 | |
| 444 | 444 | object RightAxisAutoScaleTransform: TAutoScaleAxisTransform | |
| 445 | 445 | end | |
| 446 | 446 | end | |
| 447 | 447 | end | |
| 448 | 448 | ``` | |
| 449 | 449 | ||
| 450 | 450 | ### population.pas | |
| 451 | 451 | ||
| 452 | 452 | ``` pascal | |
| 453 | 453 | unit population; | |
| 454 | 454 | ||
| 455 | 455 | {$mode objfpc}{$H+} | |
| 456 | 456 | ||
| 457 | 457 | interface | |
| 458 | 458 | ||
| 459 | 459 | uses | |
| 460 | 460 | Classes, SysUtils; | |
| 461 | 461 | ||
| 462 | 462 | type | |
| 463 | 463 | TPopulationRecord = record | |
| 464 | 464 | Age: Integer; | |
| 465 | 465 | Total: Double; | |
| 466 | 466 | Male: Double; | |
| 467 | 467 | Female: Double; | |
| 468 | 468 | Ratio: Double; | |
| 469 | 469 | end; | |
| 470 | 470 | TPopulationArray = array of TPopulationRecord; | |
| 471 | 471 | ||
| 472 | 472 | procedure LoadPopulationData(const AFileName: String; var AData: TPopulationArray); | |
| 473 | 473 | ||
| 474 | 474 | implementation | |
| 475 | 475 | ||
| 476 | 476 | procedure LoadPopulationData(const AFileName: String; var AData: TPopulationArray); | |
| 477 | 477 | ||
| 478 | 478 | function StripThousandSep(const s: String): String; | |
| 479 | 479 | // Removes the thousand separators from the string | |
| 480 | 480 | // Otherwise StrToFloat would fail. | |
| 481 | 481 | var | |
| 482 | 482 | i: Integer; | |
| 483 | 483 | begin | |
| 484 | 484 | Result := s; | |
| 485 | 485 | for i:=Length(Result) downto 1 do | |
| 486 | 486 | if Result[i] = ',' then | |
| 487 | 487 | Delete(Result, i, 1); | |
| 488 | 488 | end; | |
| 489 | 489 | ||
| 490 | 490 | var | |
| 491 | 491 | List1, List2: TStringList; | |
| 492 | 492 | i, j, n: Integer; | |
| 493 | 493 | s: String; | |
| 494 | 494 | ds: char; | |
| 495 | 495 | begin | |
| 496 | 496 | ds := FormatSettings.DecimalSeparator; | |
| 497 | 497 | List1 := TStringList.Create; | |
| 498 | 498 | try | |
| 499 | 499 | List1.LoadFromFile(AFileName); | |
| 500 | 500 | n := List1.Count; | |
| 501 | 501 | SetLength(AData, n-2); | |
| 502 | 502 | FormatSettings.DecimalSeparator := '.'; | |
| 503 | 503 | List2 := TStringList.Create; | |
| 504 | 504 | try | |
| 505 | 505 | List2.Delimiter := #9; | |
| 506 | 506 | List2.StrictDelimiter := true; | |
| 507 | 507 | j := 0; | |
| 508 | 508 | for i:=2 to n-1 do begin | |
| 509 | 509 | List2.DelimitedText := List1[i]; | |
| 510 | 510 | s := List1[i]; | |
| 511 | 511 | with AData[j] do begin | |
| 512 | 512 | if i < n-1 then | |
| 513 | 513 | Age := StrToInt(trim(List2[0])) | |
| 514 | 514 | else | |
| 515 | 515 | Age := 100; // the last line is "100 +" | |
| 516 | 516 | Total := StrToFloat(StripThousandSep(trim(List2[1]))); | |
| 517 | 517 | Male := StrToFloat(StripThousandSep(trim(List2[2]))); | |
| 518 | 518 | Female := StrToFloat(StripThousandSep(trim(List2[3]))); | |
| 519 | 519 | Ratio := StrToFloat(trim(List2[4])); | |
| 520 | 520 | end; | |
| 521 | 521 | inc(j); | |
| 522 | 522 | end; | |
| 523 | 523 | finally | |
| 524 | 524 | List2.Free; | |
| 525 | 525 | end; | |
| 526 | 526 | finally | |
| 527 | 527 | FormatSettings.DecimalSeparator := ds; | |
| 528 | 528 | List1.Free; | |
| 529 | 529 | end; | |
| 530 | 530 | end; | |
| 531 | 531 | ||
| 532 | 532 | end. | |
| 533 | 533 | ``` | |
| 534 | 534 | ||
| 535 | 535 | --- | |
| 536 | 536 | *From the Free Pascal wiki, page "TAChart Tutorial: Dual y axis, Legend/fi", as it stood on 2020-02-29 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* |
