TAChart Tutorial: Dual y axis, Legend/fi

From Lazarus wiki
Jump to navigationJump to search

English (en) suomi (fi)

Johdanto

TAChart DualYAxis11.png

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.

Kun olet käynyt läpi opetusohjelman 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

Tämä on taustana tähän projektiin. Katsotaan väestön tiedot uudelleen, piirretään väkiluku ja mies-nais suhde samaan kaavioon.

Opit miten

  • luoda kaavion, jossa on kaksi y-akselia
  • käyttää automaattista skaalausta akselin asteikko muunnoksessa
  • tehdään käyttäjän muokkaamia akselin merkintöjä
  • tehdään kuvaajien otsikkopalkki (legend) käyttämällä vähemmän tunnettuja ominaisuuksia.

Kuten tavallista niin sinun tulisi tuntea Lazarus ja FPC sekä pitäisi olla perustiedot TAChart kirjastosta (käy ensin läpi TAChart:n aloitus opetusohjelma ). Tässä nimenomaisessa opetusohjelmassa olisi myös hyödyllistä, jos olisit tutkinut opetusohjelman käyttäjän määrittämiä chartsource ensin.

Data

Kuten jo mainittiin käytämme samoja tietoja kuin käyttäjän määrittämä chartsource opetusohjelmassa. Ensisijainen datatiedosto on nimeltään "population.txt" ja peräisin www.census.gov. Kopioi tämä tiedosto 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 TPopulationRecordtaulukkoon

type
  TPopulationRecord = record
    Age: Integer;
    Total: Double;
    Male: Double;
    Female: Double;
    Ratio: Double;
  end;

  TPopulationArray = array of TPopulationRecord;

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:


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;

Kaavion valmistelu

Nyt voidaan aloittaa kaavion valmistelu...

Lisää TChart komponentti lomakkeelle:

  • Aseta sen Align ominaisuus arvoon alClient.
  • Aseta sen BackColor arvoon clWhite.

Lisää kolme viivakuvaajaa kaavioon:

  • Vaihda niiden nimet kuvailemmiksi LineSeries_male, LineSeries_female ja LineSeries_ratio.
  • Aseta SeriesColor väri kuvaajalla LineSeries_male arvoon clSkyBlue, kuvaajalla LineSeries_female arvoon $00FF80FF ja jätä LineSeries_ratio kuvaajan väri mustaksi.

Mistä kuvaajat saavat tietonsa? Tiedot on tallennettu PopulationData taulukkoon, joten se olisi parasta hyödyntää käyttäjän määrittämä kaavio lähde . Tarkasti ottaen: tarvitaan kolme kaavion lähdettä, yksi kutakin kuvaajaa kohti.

Siksi lisää kolme TUserDefinedChartSource -komponenttia lomakkeelle:

  • Nimetään ne kuvaammaksi ChartSource_male, ChartSource_female ja ChartSource_ratio.
  • Aseta kunkin chartsource ominaisuus Source vastaamaan oikeata viivakuvaajaa.
  • Kirjoitetaan seuraava tapahtumankäsittelijä ja liitetään se kunkin user-defined chart source:n OnGetDataItem tapahtumaan:
procedure TForm1.ChartSourceGetChartDataItem(
  ASource: TUserDefinedChartSource; AIndex: Integer; var AItem: TChartDataItem);
begin
  AItem.X := PopulationData[AIndex].Age;
  if ASource = ChartSource_male then
    AItem.Y := PopulationData[AIndex].Male / 1e6
  else if ASource = ChartSource_female then
    AItem.Y := PopulationData[AIndex].Female / 1e6
  else
    AItem.Y := PopulationData[AIndex].Ratio / 100;
end;

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.

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.

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ä:


procedure TForm1.FormCreate(Sender: TObject);
begin
  LoadPopulationData(POPULATION_FILE, PopulationData);
  ChartSource_male.PointsNumber := Length(PopulationData);
  ChartSource_female.PointsNumber := Length(PopulationData);
  ChartSource_ratio.PointsNumber := Length(PopulationData);
end;

Tässä vaiheessa lomake ja objekti puu pitäisi näyttää tältä:

TAChart DualYAxis1.png TAChart DualYAxis2.png

ja kun tuon kääntää ja ajaa niin tuloksen pitäisi näyttää joltain tämäntapaiselta:

TAChart DualYAxis3.png

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.

Asetetaan toinen y-akseli

Tarvitaan toinen y-akseli.

Tätä varten siirry objektipuuhun, klikkaa hiiren oikealla AxisList (oikealla Chart1:n alla ) ja lisää uusi akseli kaavioon. Nyt meillä on kolme akselia:


TAChart DualYAxis4.png

Valitse akseli #2, joka on vielä vasen akseli tällä hetkellä ja aseta komponenttimuokkaimessa sen Alignment arvoon calRight.

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 Group: saman ryhmän akselit piirretään samaan suorakulmion, akselien eri ryhmän arvot on piirretty side-by-side.

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ä.

Kun tämä käännetään niin tulos on hieman pettymys: ei muutosta - musta suhde-kuvaaja on edelleen hyvin tasainen.


TAChart DualYAxis5.png

Akselin asettaminen automaattisesti muuttuvaan mittakaavaan

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:

  • 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.
  • 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.
  • kuva koordinaatit kuuluvat pikseliä ruudulla lasketaan kuvaajan koordinaatteja.

Niin, on olemassa kaksi muunnostoimintoa mukana laskettaessa koordinaattien datapisteet näytölle:

  1. akselin koordinaatit näytön koordinaatteihin käyttäjän määrittelemän TChartAxisTransform avulla
  2. näytön koordinaatit kuvan koordinaateiksi yksinkertaisella lineaarikuvauksen avulla

Jos akselin siirtymistä akselista kaavion koordinaatteihin ei ole määritelty niin käytetään yksinkertaista 1: 1 muunnosta.

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.

Tätä tarkoitusta varten TAChart on TAutoScaleAxisTransform. Siihen ei ole suoraa pääsyä komponenttipaletilta, mutta sen lapsi löytyy: TChartAxisTransformations -komponentti.

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 .

Tältä näyttää objektipuu näiden käsittelyiden jälkeen:

TAChart DualYAxis6.png

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.

Kun käännetään ja ajetaan ohjelma niin silloin nähdään mitä tarkalleen tapahtuu.

TAChart DualYAxis7.png

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.

Hieno viilaus

Vaikein osa on tehty nyt. Jäljellä on vielä kaavion "heino viilaus".

  • Poista akseliruudukko (tämä on jo aikaisemmin opetettu). Vasemman ja oikeamman akselin viivat tekevät siitä hyvin sekavan
  • Lisää akseleiden otsikot:
    • "Väestö" tai "Population" vasemmalle akselille
    • "Mies/Nais suhde" tai "Male-to-female ratio" oikealle akselille
    • "Ikä (vuosina)" tai "Age (Years)" ala akselille.
    • Aseta akselien LabelFont.Style arvoon fsBold.
  • 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.
  • 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.

Muokataan akselin väliviivojen tekstiä

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 :

procedure TForm1.Chart1AxisList0MarkToText(var AText: String; AMark: Double);
begin
  AText := Format('%s M', [AText]);
end;

TAChart DualYAxis8.png

Asetetaan kuvaajien otsikkopalkki (legend)

Opetusohjelma on melkein valmis nyt. Mitä on jäljellä on 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.

TAChart DualYAxis9.png

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.

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?

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 Order ominaisuutta tai muuttaa omaisuutta ItemFillOrder tai ehkä ottaa käyttöön (tyhjiä kohteita).

TAChart's kuvaajien otsikkopalkki (legend) on erittäin joustava -- Ole hyvä ja katso sen dokumentaatio saadaksesi lisätietoa siitä.

Lopuksi, lisätään teksti "World population" kaavion otsikkoon ja referenssi tiedot alatunnisteeseen.


TAChart DualYAxis11.png

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
  { you can add units after this };

{$R *.res}

begin
  RequireDerivedFormResource := True;
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

Unit1.pas

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, TAGraph, TASeries, TASources, Forms, Controls,
  Graphics, Dialogs, population, TACustomSource, TATransformations, TAChartAxisUtils;

type

  { TForm1 }

  TForm1 = class(TForm)
    Chart1: TChart;
    LeftAxisTransformations: TChartAxisTransformations;
    LeftAxisAutoScaleTransform: TAutoScaleAxisTransform;
    RightAxisTransformations: TChartAxisTransformations;
    LineSeries_male: TLineSeries;
    LineSeries_female: TLineSeries;
    LineSeries_ratio: TLineSeries;
    ChartSource_male: TUserDefinedChartSource;
    ChartSource_female: TUserDefinedChartSource;
    ChartSource_ratio: TUserDefinedChartSource;
    RightAxisAutoScaleTransform: TAutoScaleAxisTransform;
    procedure Chart1AxisList0MarkToText(var AText: String; AMark: Double);
    procedure ChartSourceGetChartDataItem(
      ASource: TUserDefinedChartSource; AIndex: Integer;
      var AItem: TChartDataItem);
    procedure FormCreate(Sender: TObject);
  private
    { private declarations }
    PopulationData: TPopulationArray;
  public
    { public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

const
  POPULATION_FILE = 'population.txt';

{ TForm1 }

procedure TForm1.ChartSourceGetChartDataItem(
  ASource: TUserDefinedChartSource; AIndex: Integer; var AItem: TChartDataItem);
begin
  AItem.X := PopulationData[AIndex].Age;
  if ASource = ChartSource_male then
    AItem.Y := PopulationData[AIndex].Male / 1e6
  else if ASource = ChartSource_female then
    AItem.Y := PopulationData[AIndex].Female / 1e6
  else
    AItem.Y := PopulationData[AIndex].Ratio / 100;
end;

procedure TForm1.Chart1AxisList0MarkToText(var AText: String; AMark: Double);
begin
  AText := Format('%s M', [AText]);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  LoadPopulationData(POPULATION_FILE, PopulationData);
  ChartSource_male.PointsNumber := Length(PopulationData);
  ChartSource_female.PointsNumber := Length(PopulationData);
  ChartSource_ratio.PointsNumber := Length(PopulationData);
end;

end.

Unit1.lfm

object Form1: TForm1
  Left = 365
  Height = 409
  Top = 169
  Width = 470
  Caption = 'Form1'
  ClientHeight = 409
  ClientWidth = 470
  OnCreate = FormCreate
  LCLVersion = '1.1'
  object Chart1: TChart
    Left = 0
    Height = 409
    Top = 0
    Width = 470
    AxisList = <    
      item
        Grid.Visible = False
        Minors = <>
        Title.LabelFont.Orientation = 900
        Title.LabelFont.Style = [fsBold]
        Title.Visible = True
        Title.Caption = 'Population'
        Transformations = LeftAxisTransformations
        OnMarkToText = Chart1AxisList0MarkToText
      end    
      item
        Grid.Visible = False
        Alignment = calBottom
        Minors = <>
        Title.LabelFont.Style = [fsBold]
        Title.Visible = True
        Title.Caption = 'Age (Years)'
      end    
      item
        Grid.Visible = False
        Alignment = calRight
        Minors = <>
        Range.UseMin = True
        Title.LabelFont.Orientation = 900
        Title.LabelFont.Style = [fsBold]
        Title.Visible = True
        Title.Caption = 'Male-to-female ratio'
        Transformations = RightAxisTransformations
      end>
    BackColor = clWhite
    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
    Legend.Alignment = laBottomCenter
    Legend.ColumnCount = 2
    Legend.GroupFont.Style = [fsBold]
    Legend.GroupTitles.Strings = (
      'left:'
      'right:'
    )
    Legend.Visible = True
    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 LineSeries_male: TLineSeries
      Legend.GroupIndex = 0
      Title = 'male'
      AxisIndexY = 0
      LinePen.Color = clSkyBlue
      Source = ChartSource_male
    end
    object LineSeries_female: TLineSeries
      Legend.GroupIndex = 0
      Title = 'female'
      AxisIndexY = 0
      LinePen.Color = 16744703
      Source = ChartSource_female
    end
    object LineSeries_ratio: TLineSeries
      Legend.GroupIndex = 1
      Title = 'ratio'
      AxisIndexY = 2
      Source = ChartSource_ratio
    end
  end
  object ChartSource_male: TUserDefinedChartSource
    OnGetChartDataItem = ChartSourceGetChartDataItem
    left = 130
    top = 25
  end
  object ChartSource_female: TUserDefinedChartSource
    OnGetChartDataItem = ChartSourceGetChartDataItem
    left = 130
    top = 79
  end
  object ChartSource_ratio: TUserDefinedChartSource
    OnGetChartDataItem = ChartSourceGetChartDataItem
    left = 130
    top = 137
  end
  object LeftAxisTransformations: TChartAxisTransformations
    left = 267
    top = 25
    object LeftAxisAutoScaleTransform: TAutoScaleAxisTransform
    end
  end
  object RightAxisTransformations: TChartAxisTransformations
    left = 267
    top = 136
    object RightAxisAutoScaleTransform: TAutoScaleAxisTransform
    end
  end
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;
  TPopulationArray = array of TPopulationRecord;

procedure LoadPopulationData(const AFileName: String; var AData: TPopulationArray);

implementation

procedure LoadPopulationData(const AFileName: String; var AData: TPopulationArray);

  function StripThousandSep(const s: String): String;
  // Removes the thousand separators from the string
  // Otherwise StrToFloat would fail.
  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(AData, 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 AData[j] do begin
          if i < n-1 then
            Age := StrToInt(trim(List2[0]))
          else
            Age := 100;  // the last line is "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.