HotXLS renderuje dátumové osi Excel grafov kalendárnou aritmetikou, nie pevnými počtami dní. Trieda TXLSChartDateAxisTransform v unit lxChart drží každú súradnicu ako workbook serial, počíta každý tick mesiaca alebo roka priamo z minima osi cez IncMonth a IncYear a prichytáva konce mesiacov na reálny kalendár, takže séria ukotvená na 31. januára dostane ticky na 28. februára, 31. marca a 30. apríla namiesto toho, aby sa kĺzala. Ten istý transform vyberá jednotky dňa, mesiaca alebo roka automaticky a zdieľajú ho renderery grafov HTML, SVG aj stránkovaného PDF
Chyba, ktorej to predchádza, je povedomá každému, kto niekedy grafoval mesačné uzávierky. Renderer, ktorý berie mesiac ako 30 dní, drí o päť dní skôr už na konci prvého roka. Renderer, ktorý je múdrejší a používa kalendárne mesiace, ale posúva každý tick od predchádzajúceho, zlyháva potichšie: 31. januára sa stane 28. februárom, ďalší krok pristane na 28. marca a každý tick po ňom zostáva pripútaný na 28. Graf vyzerá vierohodne, štítky sú zlé a nikto si nič nevšimne, kým kontrolór sa nepýta, prečo je marcový zostatok zakreslený tri dni pred koncom štvrťroka
Prečo ticky mesiacov drifujú, keď sa akumulujú?
Ticky mesiacov drifujú, pretože prichytenie vyhodí informáciu. Keď je raz 31. januára prichytený na 28. februára, fakt, že séria chcela tridsiatku, je preč a každý krok urobený z prichyteného dátumu zdedí tú stratu. TXLSChartDateAxisTransform.BuildTicks nikdy nekročí z predchádzajúceho ticku. Tick i sa vždy počíta ako AddUnits(MinValue, UnitKind, Step * i, ...), čo je min + jednotka × index merané od minima osi. AddUnits pripočíta počet priamo k serialu pre jednotky dní a pre jednotky mesiacov a rokov skonvertuje serial na reálny dátum, zavolá IncMonth alebo IncYear a skonvertuje späť. Slučka je ohraničená zo všetkých strán: výsledné pole má strop 4096 položiek, nech si volajúci pýta čokoľvek, Step * i sa kontroluje proti MaxInt skôr, než sa vynásobí, a generácia sa zastaví, hneď ako tick prestane rásť alebo prekročí maximum osi
uses
SysUtils, lxChart;
var
Ticks: TXLSChartDateValues;
MinSerial, MaxSerial: Double;
I, Count: Integer;
begin
// Dátumový systém 1900 (Dates1904 = False), séria ukotvená na konci mesiaca
MinSerial := TXLSChartDateAxisTransform.DateTimeToSerial(
EncodeDate(2026, 1, 31), False);
MaxSerial := TXLSChartDateAxisTransform.DateTimeToSerial(
EncodeDate(2026, 12, 31), False);
// Krok jeden mesiac, cieľ 12 tickov, nikdy viac než 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;
V BuildTicks je jedna úprimná aproximácia. Keď je požadovaný krok nula, metóda odhadne veľkosť kroku tým, že berie mesiac ako 30 dní a rok ako 365, a potom zaokrúhľuje surový krok na násobok 1, 2 alebo 5 mocniny desiatky. Ten odhad rozhoduje len o tom, koľko jednotiek leží medzi tickmi. Pozície tickov samotných stále pochádzajú z IncMonth a IncYear, takže aproximácia môže zmeniť hustotu tickov, ale nikdy neposunie tick z kalendára
Prečo držať workbook sery namiesto konverzie na TDateTime?
Dátumová os v HotXLS drží workbook sery ako svoj súradnicový priestor preto, lebo Delphi TDateTime nemá slot pre serial 60, fiktívny 29. február 1900, ktorý dátumový systém 1900 zdedil ako kvirk kvôli kompatibilite. TrySerialToDateTime posúva sery pod 60 o jeden deň a zliepa serial 60 na 28. februára, čo je pri označovaní jedného bodu v poriadku, ale pre geometriu osudné: skonvertujte najprv každý bod na TDateTime a dva dni okolo fiktívneho dna skončia o jednu jednotku bližšie pri sebe, než ich kreslí Excel, takže všetko zakreslené pred marcom 1900 sa posunie relatívne voči všetkému po ňom. TXLSChartDateAxisTransform.FormatValue spracováva ten istý slot ako špeciálny prípad a označí ho 1900-02-29 presne ako Excel. V workbooku 1904 je serial 0 = 1. január 1904 a transform aplikuje pevný offset 1462 dní oboma smermi; celý príbeh oboch epoch je v našom článku o Excel date sere, systéme 1904 a number formátoch
Ako si HotXLS vyberá dni, mesiace alebo roky automaticky?
TXLSChartDateAxisTransform.DetectUnit vyberá najjemnejšiu kalendárnu jednotku, ktorú medzery v dátach reálne potrebujú. Metóda najprv zahodí NaN a nekonečné hodnoty, raz zotriedi zvyšok v O(n log n) a preskočí duplicitné dátumy. Ak je ktorákoľvek susedná medzera kratšia ako jeden kalendárny mesiac, jednotkou sú dni; inak, ak je ktorákoľvek medzera kratšia ako jeden kalendárny rok, jednotkou sú mesiace; inak roky. Test je IncMonth(Previous, 1) > Current, nie prah 30 dní, takže mesačná séria 31. januára, 28. februára, 31. marca sa správne deteguje ako mesačné dáta. Pri menej než dvoch rôznych platných dátumoch nie je čo merať a metóda padne späť na dni
ResolveUnits potom zlúči detegovanú jednotku s tým, čo deklaruje graf. Explicitná base jednotka sa rešpektuje a chýbajúca major alebo minor jednotka zdedí detegovanú jednotku, keď je hrubšia než base. Posledný krok je ten, ktorý ľudia prehliadnu: ak je efektívna major alebo minor jednotka jemnejšia než base jednotka, base sa zníži, aby sedela. Base jednotka je to, na čo sa normalizujú dátové body skôr, než vôbec existujú ticky, s Normalize, ktoré prichytí hodnotu na jej deň, prvého v mesiaci alebo 1. januára, takže mesačná base pod dennou major jednotkou by zrútila mesiac bodov do jedného slotu skôr, než by ich tick kód vôbec videl. Hustota tickov je tiež ohraničená — dĺžkou plotu, nameranou šírkou fontu osi, number formátom štítkov a projekciou otočených štítkov — a každý backend kreslí štítky, tick marky a gridlines z jedného zdieľaného, ohraničeného poľa tickov, pritom minor tick, ktorý sa prekryje s major tickom, sa kreslí len raz
Čo ukladá element dateAx a čo znamená jeho vynechanie?
V ChartML je každé kalendárne nastavenie dátumovej osi voliteľné a vynechanie nie je to isté ako zapísanie predvolenej hodnoty. Typ CT_DateAx v ECMA-376 Part 1, §21.2 (DrawingML Charts) nechá každý z baseTimeUnit, majorTimeUnit, minorTimeUnit a auto objaviť sa alebo nie, a vynechaný baseTimeUnit hovorí Excelu, nech sa rozhodne sám, čo je niečo iné než explicitný days. TXLSXChartAxis preto ukladá každú hodnotu vedľa presence flagu: BaseTimeUnit s BaseTimeUnitSet, MajorTimeUnit s MajorTimeUnitSet, MinorTimeUnit s MinorTimeUnitSet a AutoDateAxis s AutoDateAxisSet. Priradenie hodnoty nastaví jej flag, zrušenie flagu vráti element do stavu vynechania a XLSX writer emituje element len vtedy, keď jeho flag je nastavený. Všimnite si, že auto znamená automatickú detekciu category verzus date, nie automatické tick jednotky
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; // zároveň nastaví BaseTimeUnitSet
Axis.MajorTimeUnit := xlsxChartTimeMonths;
Axis.MajorUnit := 1; // > 0 označí majorUnit ako nastavený
Axis.NumberFormat := 'mmm yyyy';
Axis.NumberFormatSourceLinked := False;
Axis.TextStyle.Rotation := -45; // stupne; ukladá sa ako 1/60000
// AutoDateAxis nedotknuté: element c:auto sa nezapisuje
Book.SaveAs('month-end-balance.xlsx');
finally
Book.Free;
end;
end;
Štítky a orientácia nasledujú tie isté presence-aware pravidlá. numFmt osi sedí za titulom a pred tick markmi, jeho atribút sourceLinked má predvolené true a TXLSXChartAxis drží formát kód, source-linked hodnotu a NumberFormatSet oddelene, takže prázdny kód sa nikdy nepomieňa za neprítomný element. Otočenie textu sa ukladá v 1/60000 stupňa, pritom kladné hodnoty znamenajú v smere hodinových ručičiek; TextStyle.Rotation berie obyčajné stupne, SVG výstup používa uhol tak, ako je, a stránkovaný backend, ktorého kladné uhly bežia proti smeru hodinových ručičiek, prevráti znamienko na jednej zdieľanej hranici. Obrátená os maxMin zrkadlí major aj minor ticky, gridlines, dátové body, trendlines aj error bary spolu, a nikdy len štítky. Keď otvoríte workbook napísaný inde, flagy vám povedia, čo autor reálne špecifikoval
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; // uložte ho znova a vynechané elementy zostanú vynechané
end;
end;
Kde kalendárny model končí?
Kalendárny model končí pri formátoch, ktoré nedokážu vyjadriť kalendárne jednotky, a HotXLS sa nesnaží tváriť inak. ODF 1.2 ponúka chart:interval-major, čo je holé číslo bez predstavy dní, mesiacov či rokov, takže mesačná os Excelu uložená ako ODS nedokáže niesť svoju jednotku a HotXLS nevymýšľa nestandardný atribút, aby jednu zfaloval. Na legacy strane BIFF8 drží record AxcExt deväť pevných 16-bitových slov a jeho automatické flagy polia len maskujú, namiesto aby ich odstránili; HotXLS zachováva maskované minimum, maximum, interval, jednotku a crossing hodnoty pri čítaní aj zápise späť, ponecháva neznáme unit kódy nedotknuté v Classic modeli a mapuje do XLSX len platné date jednotky 0, 1 a 2 (dni, mesiace, roky)
Dátumové osi sú jedna vrstva chart modelu, ktorý musí prežiť aj súbory, ktoré sám nevytvoril. Keď Excel workbook kombinuje date-axis line so stĺpcovou sériou na sekundárnej osi, náš článok o zachovanom ChartML a combination chartoch ukazuje, ako HotXLS prehráva pôvodný chart XML bajt za bajtom, keď sa nič nezmenilo, a zlučuje typované úpravy do neho, keď sa niečo zmenilo, a náš prehľad grafov, obrázkov a drawings pokrýva anchoring a series API použité vyššie. Všetko to obsahuje HotXLS Delphi Component pre Delphi a C++Builder, ktorý číta, zapisuje a renderuje XLS a XLSX grafy bez Excel automation