Tehnički članak

Datumska osa grafikona u HotXLS-u tačna po kalendaru

HotXLS iscrtava datumske ose Excel grafikona kalendarskom aritmetikom, ne fiksnim brojem dana. Klasa TXLSChartDateAxisTransform u jedinici lxChart drži svaku koordinatu kao workbook serial, računa svaki mesečni ili godišnji tick direktno iz minimuma ose kroz IncMonth i IncYear, i steže krajeve meseca na pravi kalendar, pa serija zakačena za 31. januar dobija tick-ove na 28. februar, 31. mart i 30. april umesto da klizi. Isti transform automatski bira jedinice dana, meseca ili godine i dele ga HTML, SVG i paginirani PDF chart rendereri

Bug koji ovo sprečava poznat je svakome ko je ikada grafikonom preslikao mesečno zatvaranje. Renderer koji mesec tretira kao 30 dana odstupi pet dana unazad do kraja prve godine. Renderer koji je pametniji i koristi kalendarske mesece, ali napreduje svaki tick od prethodnog, omane tiše: 31. januar postane 28. februar, sledeći korak doskoči na 28. mart, i svaki tick posle toga ostane zakačen za 28-i. Grafikon deluje uverljivo, natpisi su pogrešni, i niko ne primeti dok knjigovođa ne pita zašto je martovski saldo nacrtan tri dana pre kraja kvartala

Zašto mesečni tick-ovi odstupaju kad se akumuliraju?

Mesečni tick-ovi odstupaju jer stezanje baca informaciju. Kad jednom 31. januar bude stežen na 28. februar, činjenica da je serija želela 31. nestala je, i svaki korak od stežene vrednosti nasleđuje gubitak. TXLSChartDateAxisTransform.BuildTicks nikada ne kroči od prethodnog tick-a. Tick i se uvek računa kao AddUnits(MinValue, UnitKind, Step * i, ...), što je min + jedinica × indeks mereno od minimuma ose. AddUnits za jedinice dana dodaje broj pravo na serial, a za jedinice meseca i godine konvertuje serial u pravi datum, pozove IncMonth ili IncYear i konvertuje nazad. Petlja je takođe ograničena sa svake strane: niz rezultata je pokriven na 4096 unosa šta god pozivalac tražio, Step * i se proverava prema MaxInt pre množenja, i generacija staje čim tick ne uspe da poraste ili pređe maksimum ose

Dva načina na koje HotXLS datumska osa grafikona može da postavi mesečne tick-ove posle sidra 31. januar 2026: napredovanje svakog tick-a od prethodne stežene vrednosti ostaje zakačeno za 28-i, dok BuildTicks računa AddUnits(MinValue, xcduMonths, Step puta i) od minimuma ose pa tick-ovi doskoče na 28. februar, 31. mart i 30. april
Stezanje baca informaciju, pa TXLSChartDateAxisTransform nikada ne kroči od prethodnog tick-a i preračunava svaku poziciju od minimuma ose, stežući krajeve meseca na pravi kalendar
uses
  SysUtils, lxChart;

var
  Ticks: TXLSChartDateValues;
  MinSerial, MaxSerial: Double;
  I, Count: Integer;
begin
  // 1900 datumski sistem (Dates1904 = False), serija zakačena za kraj meseca
  MinSerial := TXLSChartDateAxisTransform.DateTimeToSerial(
    EncodeDate(2026, 1, 31), False);
  MaxSerial := TXLSChartDateAxisTransform.DateTimeToSerial(
    EncodeDate(2026, 12, 31), False);

  // Korak od jednog meseca, cilj 12 tick-ova, nikad više od 64
  Count := TXLSChartDateAxisTransform.BuildTicks(MinSerial, MaxSerial,
    1, xcduMonths, False, 12, 64, Ticks);

  for I := 0 to Count - 1 do
    Writeln(TXLSChartDateAxisTransform.FormatValue(Ticks[I], False));
  // 2026-01-31, 2026-02-28, 2026-03-31, 2026-04-30 ... 2026-12-31
end;

U BuildTicks-u postoji jedna iskrena aproksimacija. Kad je traženi korak nula, metoda procenjuje veličinu koraka tretirajući mesec kao 30 dana i godinu kao 365, pa zaokružuje sirovi korak na 1, 2 ili 5 puta stepen desetice. Ta procena odlučuje samo koliko jedinica leži između tick-ova. Same pozicije tick-ova i dalje dolaze iz IncMonth i IncYear, pa aproksimacija može da promeni gustinu tick-ova ali nikada ne pomeri tick van kalendara

Zašto čuvati workbook serial umesto konverzije u TDateTime?

Datumska osa u HotXLS-u čuva workbook serial-e kao svoj koordinatni prostor jer Delphi TDateTime nema mesta za serial 60, fantomski 29. februar 1900 koji je 1900 datumski sistem nasledio kao kvir kompatibilnosti. TrySerialToDateTime pomera serial-e ispod 60 za jedan dan i preklapa serial 60 na 28. februar, što je u redu za označavanje jedne tačke ali kobno za geometriju: konvertujte prvo svaku tačku u TDateTime pa se dva dana oko fantomskog dana nađu jednu jedinicu bliže nego što ih Excel crta, pa se sve nacrtano pre marta 1900 pomeri u odnosu na sve posle njega. TXLSChartDateAxisTransform.FormatValue tretira isti slot posebno i označava ga kao 1900-02-29 baš kao Excel. U 1904 workbook-u, serial 0 je 1. januar 1904, i transform primenjuje fiksni pomeraj od 1462 dana u oba smera; cela priča o obe epohe je u našem članku o Excel datumskim serialima, 1904 sistemu i formatima brojeva

Zašto HotXLS datumska osa čuva workbook serial umesto konverzije u TDateTime: serial 60 je fantomski 29. februar 1900, TrySerialToDateTime pomera serial-e ispod 60 i preklapa 60 na 28. februar pa se dani oko njega nađu jednu jedinicu bliže nego što ih Excel crta, dok FormatValue označava serial 60 kao 1900-02-29
Preklapanje je u redu za označavanje jedne tačke ali kobno za geometriju, jer se sve nacrtano pre marta 1900 pomeri prema svemu posle njega; 1904 workbook umesto toga primenjuje fiksni pomeraj od 1462 dana

Kako HotXLS automatski bira dane, mesece ili godine?

TXLSChartDateAxisTransform.DetectUnit bira najfiniju kalendarsku jedinicu koju praznine u podacima stvarno traže. Metoda prvo odbacuje NaN i beskonačne vrednosti, jednom sortira ostatak u O(n log n), i preskače duplikate datuma. Ako je bilo koji susedni razmak kraći od jednog kalendarskog meseca, jedinica su dani; inače, ako je bilo koji razmak kraći od jedne kalendarske godine, jedinica su meseci; inače su godine. Test je IncMonth(Previous, 1) > Current, ne prag od 30 dana, pa se serija kraj meseca 31. januar, 28. februar, 31. mart ispravno detektuje kao mesečni podaci. Sa manje od dva različita validna datuma nema razmaka za merenje, i metoda se vraća na dane

ResolveUnits zatim spaja detektovanu jedinicu sa onim što grafikon deklariše. Eksplicitna bazna jedinica se poštuje, a nedostajuća major ili minor jedinica nasleđuje detektovanu kad je ona grublja od bazne. Poslednji korak je onaj koji ljudi promaše: ako je efektivna major ili minor jedinica finija od bazne, baza se spušta da se poklopi. Bazna jedinica je ono na šta se tačke podataka normalizuju pre nego što ijedan tick postoji, a Normalize zakači vrednost za njen dan, prvi u njegovom mesecu ili 1. januar, pa bi mesečna baza ispod dnevne major jedinice skupila mesec tačaka u jedan slot pre nego što tick kod ikada vidi. Gustina tick-ova je takođe ograničena, dužinom prikaza, izmerenom širinom fonta ose, formatom broja natpisa i projekcijom rotiranih natpisa, i svaki backend crta natpise, tick oznake i gridlines iz jednog zajedničkog, ograničenog niza tick-ova, pri čemu se minor tick koji se poklopi sa major tick-om crta samo jednom

Kako HotXLS automatski bira jedinice datumske ose: DetectUnit odbacuje NaN i beskonačne vrednosti, jednom sortira i meri kalendarske razmake sa IncMonth umesto praga od 30 dana, pa se serija 31. januar, 28. februar, 31. mart detektuje kao mesečna, a ResolveUnits poštuje eksplicitnu bazu uz spuštanje baze kad je major jedinica finija
Normalize zakače svaku vrednost za njen dan, prvi u mesecu ili 1. januar pre nego što ijedan tick postoji, jer bi mesečna baza ispod dnevne major jedinice skupila mesec tačaka u jedan slot

Šta čuva element dateAx i šta znači izostavljanje?

U ChartML je svako kalendarsko podešavanje datumske ose opciono, i izostavljanje nije isto što i upis podrazumevane vrednosti. Tip CT_DateAx u ECMA-376 Part 1, §21.2 (DrawingML Charts) dozvoljava da se baseTimeUnit, majorTimeUnit, minorTimeUnit i auto svaki pojavi ili ne, i izostavljeni baseTimeUnit govori Excelu da odluči sam, što je drugačije od eksplicitnog days. TXLSXChartAxis zato čuva svaku vrednost pored zastavice prisutnosti: BaseTimeUnit sa BaseTimeUnitSet, MajorTimeUnit sa MajorTimeUnitSet, MinorTimeUnit sa MinorTimeUnitSet, i AutoDateAxis sa AutoDateAxisSet. Dodela vrednosti postavlja zastavicu, brisanje zastavice vraća element u izostavljanje, i XLSX pisac emituje element samo kad je zastavica postavljena. Napomena: auto znači automatsku detekciju kategorija-naspram-datuma, ne automatske jedinice tick-ova

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Chart: TXLSXChart;
  Axis: TXLSXChartAxis;
  M: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Close');
    Sheet.Cells[1, 1].Value := 'Month end';
    Sheet.Cells[1, 2].Value := 'Balance';
    for M := 1 to 12 do
    begin
      Sheet.Cells[M + 1, 1].Value :=
        EncodeDate(2026, M, DaysInAMonth(2026, M));   // DateUtils
      Sheet.Cells[M + 1, 2].Value := 1000 + M * 75;
    end;

    Chart := Sheet.AddLineChart('Month-end balance',
      'Close!$A$2:$A$13', 'Close!$B$2:$B$13', 15, 1, 32, 9);
    Axis := Chart.CategoryAxis;
    Axis.Kind := xlsxAxisDate;                  // zapisuje c:dateAx
    Axis.BaseTimeUnit := xlsxChartTimeDays;     // takođe postavlja BaseTimeUnitSet
    Axis.MajorTimeUnit := xlsxChartTimeMonths;
    Axis.MajorUnit := 1;                        // > 0 označava majorUnit kao postavljen
    Axis.NumberFormat := 'mmm yyyy';
    Axis.NumberFormatSourceLinked := False;
    Axis.TextStyle.Rotation := -45;             // stepeni; čuva se kao 1/60000
    // AutoDateAxis netaknut: element c:auto se ne zapisuje

    Book.SaveAs('month-end-balance.xlsx');
  finally
    Book.Free;
  end;
end;

Natpisi i orijentacija prate ista pravila svesna prisutnosti. Osa numFmt sedi posle naslova i pre tick oznaka, njen atribut sourceLinked podrazumeva true, i TXLSXChartAxis drži kod formata, source-linked vrednost i NumberFormatSet odvojeno, pa se prazan kod nikada ne pomeša sa odsutnim elementom. Rotacija teksta čuva se u 1/60000 stepena, pri čemu pozitivne vrednosti znače smer kazaljke; TextStyle.Rotation prima obične stepene, SVG izlaz koristi ugao kakav jeste, a paginirani backend, čiji pozitivni uglovi idu suprotno od kazaljke, okrene znak na jednoj zajedničkoj granici. Obrnuta maxMin osa ogleda major i minor tick-ove, gridlines, tačke podataka, trend linije i error barove zajedno, i nikada samo natpise. Kad otvorite workbook pisan negde drugde, zastavice Vam govore šta je autor stvarno specificirao

var
  Book: TXLSXWorkbook;
  Axis: TXLSXChartAxis;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.Open('forecast.xlsx') <> 1 then
      raise Exception.Create('Cannot open workbook');
    Axis := Book.Sheets[0].Charts[0].CategoryAxis;
    if Axis.Kind = xlsxAxisDate then
    begin
      if Axis.BaseTimeUnitSet then
        Writeln('baseTimeUnit = ', Ord(Axis.BaseTimeUnit))
      else
        Writeln('baseTimeUnit omitted: Excel chooses at render time');
      if Axis.AutoDateAxisSet then
        Writeln('auto = ', Axis.AutoDateAxis);
    end;
  finally
    Book.Free;   // sačuvajte ponovo i izostavljeni elementi ostaju izostavljeni
  end;
end;

Gde se model kalendara zaustavlja?

Model kalendara staje kod formata koji ne mogu da izraze kalendarske jedinice, i HotXLS se ne pravi da je drugačije. ODF 1.2 nudi chart:interval-major, običan broj bez pojma dana, meseca ili godine, pa Excel mesečna osa sačuvana kao ODS ne može da ponese svoju jedinicu, a HotXLS ne izmišlja nestandardni atribut da je simulira. Na legacy BIFF8 strani, record AxcExt drži devet fiksnih 16-bitnih reči, i njegove automatic zastavice samo maskiraju polja umesto da ih uklone; HotXLS čuva maskirane vrednosti minimuma, maksimuma, intervala, jedinice i preseka pri čitanju i upisu nazad, drži nepoznate kodove jedinica netaknute u Classic modelu, i mapira samo validne datumske jedinice 0, 1 i 2 (dani, meseci, godine) u XLSX

Datumske ose su jedan sloj modela grafikona koji takođe mora da preživi fajlove koje nije sam napravio. Kad Excel workbook kombinuje liniju sa datumskom osom i kolonsku seriju na sekundarnoj osi, naš članak o očuvanom ChartML-u i kombinovanim grafikonima pokazuje kako HotXLS reprizira originalni chart XML bajt po bajt kad se ništa nije promenilo i utapa tipizirane izmene u njega kad se nešto ipak promenilo, a naš pregled grafikona, slika i crteža pokriva API-je sidrenja i serija korišćene gore. Sve to isporučuje se u HotXLS Delphi komponenti za Delphi i C++Builder, koja čita, zapisuje i iscrtava XLS i XLSX grafikone bez Excel automatizacije