A HotXLS naptári számtannal, nem rögzített napszámmal rajzolja meg az Excel-diagramok dátumtengelyeit. A lxChart egység TXLSChartDateAxisTransform osztálya minden koordinátát munkafüzet-dátumszámként tart, minden hónap- vagy évtick-et közvetlenül a tengelyminimumból számol IncMonth és IncYear segítségével, a hónapvégeket pedig a valódi naptárra igazítja, így a január 31-ére horgonyzott sorozat február 28-ai, március 31-ei és április 30-ai tick-eket kap, a csúszás helyett. Ugyanez a transzformáció nap-, hónap- vagy év egységet választ automatikusan, és közös a HTML, az SVG és a lapozható PDF diagramrajzolók számára
A hiba, amelyet ez megelőz, mindenki számára ismerős, aki már diagramozott hónapvégi zárást. A hónapot 30 napként kezelő rajzoló az első év végére öt nappal korábbra csúszik. Az okosabb, naptári hónapokat használó, de minden tick-et az előzőből léptető rajzoló csendesebben hibázik: a január 31-ből február 28 lesz, a következő lépés március 28-ra ér, és minden utána következő tick a 28-án ragad. A diagram hihetőnek tűnik, a címkék hibásak, és addig nem veszi észre senki, míg egy kontroller meg nem kérdezi, miért van a márciusi egyenleg a negyedév vége előtt három nappal felrajzolva
Miért csúsznak a hónaptick-ek, amikor összegezik őket?
A hónaptick-ek azért csúsznak, mert a levágás információt dob el. Amint a január 31-ből február 28 lett, elveszett az a tény, hogy a sorozat a 31-ét akarta, és a levágott értékből tett minden lépés örökli a veszteséget. A TXLSChartDateAxisTransform.BuildTicks soha nem lép egy előző tick-ből. Az i-edik tick mindig AddUnits(MinValue, UnitKind, Step * i, ...) alakban számolódik, ami a tengelyminimumtól mért min + egység × index. Az AddUnits a nap egységeknél a számot egyenesen a dátumszámhoz adja, hónap és év egységeknél a dátumszámot valódi dátummá alakítja, meghívja az IncMonth-ot vagy az IncYear-t, és visszakonvertál. A ciklus minden irányban korlátos: az eredménytömb a hívó kérésétől függetlenül 4096 bejegyzésnél tetőzik, a Step * i kifejezést szorzás előtt MaxInt ellenére ellenőrzik, a generálás pedig abban a pillanatban megáll, amikor egy tick nem növekszik, vagy átlépi a tengelymaximumot
uses
SysUtils, lxChart;
var
Ticks: TXLSChartDateValues;
MinSerial, MaxSerial: Double;
I, Count: Integer;
begin
// 1900-as dátumrendszer (Dates1904 = False), hónapvégre horgonyzott sorozat
MinSerial := TXLSChartDateAxisTransform.DateTimeToSerial(
EncodeDate(2026, 1, 31), False);
MaxSerial := TXLSChartDateAxisTransform.DateTimeToSerial(
EncodeDate(2026, 12, 31), False);
// Egyhónapos lépés, 12 tick a cél, soha nem több 64-nél
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;
A BuildTicks-ban egyetlen becsületes közelítés van. Ha a kért lépés nulla, a metódus úgy becsül lépésméretet, hogy egy hónapot 30 napnak, egy évet 365-nek vesz, majd a nyers lépést tízhatvány 1, 2 vagy 5 szeresére kerekíti. Ez a becslés csak azt dönti el, hány egység esik két tick közé. Maguk a tick-pozíciók továbbra is az IncMonth-ból és az IncYear-ből jönnek, tehát a közelítés megváltoztathatja a tick-sűrűséget, de egy tick-et soha nem mozdít el a naptárból
Miért marad a tengely munkafüzet-dátumszámnál a TDateTime-ra konvertálás helyett?
A HotXLS dátumtengelye azért tartja koordinátatérként a munkafüzet-dátumszámokat, mert a Delphi TDateTime típusában nincs hely a 60-as sorszámnak, a kísértet február 29, 1900-nak, amelyet a 1900-as dátumrendszer kompatibilitási furcsaságként örökölt. A TrySerialToDateTime a 60 alatti sorszámokat egy nappal eltolja, a 60-ast pedig február 28-ra hajtja össze, ami egyetlen pont címkézéséhez jó, geometriához végzetes: ha előbb minden pontot TDateTime-ra konvertálunk, a kísértetnap két oldalán lévő napok egy egységgel közelebb kerülnek egymáshoz, mint ahogy az Excel rajzolja őket, így minden, 1900 márciusa előtt felrajzolt dolog elcsúszik mindahhoz képest, ami utána jön. A TXLSChartDateAxisTransform.FormatValue ugyanezt a helyet kezeli kivételként, és pontosan úgy címkézi 1900-02-29-ként, ahogy az Excel. Egy 1904-es munkafüzetben a 0. sorszám az 1904. január 1., a transzformáció pedig mindkét irányban alkalmazza a rögzített 1462 napos eltolást; mindkét korszak teljes története a Excel dátumsorszámokról, az 1904-es rendszerről és a számformátumokról szóló cikkünkben olvasható
Hogyan választ a HotXLS automatikusan nap, hónap vagy év közül?
A TXLSChartDateAxisTransform.DetectUnit a legfinomabb naptári egységet választja, amelyre az adathézagok ténylegesen szükségesek. A metódus előbb eldobja a NaN és a végtelen értékeket, a többit egyszer rendezi O(n log n) alatt, és átugorja a duplikált dátumokat. Ha bármely szomszédos rés rövidebb egy naptári hónapnál, az egység a nap; ellenkező esetben, ha bármely rés rövidebb egy naptári évnél, az egység a hónap; különben az év. A teszt az IncMonth(Previous, 1) > Current, nem egy 30 napos küszöb, ezért a január 31, február 28, március 31 sorozat helyesen hónapos adatként detektálódik. Kétnél kevesebb különböző érvényes dátum esetén nincs mérhető rés, és a metódus a napra esik vissza
A ResolveUnits ezután összeolvasztja a detektált egységet azzal, amit a diagram deklarál. Az explicit bázisegységet tiszteletben tartja, a hiányzó major vagy minor egység pedig akkor örökli a detektált egységet, ha az durvább a bázisnál. Az utolsó lépés az, amelyet a legtöbben kihagynak: ha a tényleges major vagy minor egység finomabb a bázisegységnél, a bázis leereszkedik hozzá. A bázisegység az, amelyhez az adatpontok normalizálódnak, még mielőtt egyetlen tick is létezne, a Normalize az értéket a napjára, a hónapja elsejére vagy január 1-jére pattintja, tehát egy havi bázis egy napi major egység alatt egy hónapnyi pontot zsúfolna egyetlen helyre, mielőtt a tick-kód egyáltalán meglátná őket. A tick-sűrűség is korlátos, a rajzterület hossza, a tengelybetűtípus mért szélessége, a címkék számformátuma és a forgatott címkék vetülete szab határt, és minden backend egyetlen, közös, korlátos tick-tömbből rajzol címkéket, tick-jeleket és rácsvonalakat, a major tick-kel egybeeső minor tick-et pedig csak egyszer
Mit tárol a dateAx elem, és mit jelent az elhagyása?
A ChartML-ben minden naptári beállítás opcionális egy dátumtengelyen, és az elhagyása nem egyenlő az alapérték kiírásával. Az ECMA-376 Part 1, §21.2 (DrawingML Charts) CT_DateAx típusában a baseTimeUnit, majorTimeUnit, minorTimeUnit és auto egyaránt megjelenhet vagy hiányozhat, és egy elhagyott baseTimeUnit azt üzeni az Excelnek, hogy döntsön maga — ez más, mint az explicit days. A TXLSXChartAxis ezért minden értéket egy jelenlét-jelző mellett tárol: BaseTimeUnit a BaseTimeUnitSet-tel, MajorTimeUnit a MajorTimeUnitSet-tel, MinorTimeUnit a MinorTimeUnitSet-tel, valamint az AutoDateAxis az AutoDateAxisSet-tel. Értékadás beállítja a jelzőt, a jelző törlése visszaadja az elemet az elhagyottnak, és az XLSX író csak akkor bocsát ki elemet, ha a jelzője áll. Figyeljen rá, hogy az auto automatikus kategória-vagy-dátum felismerést jelent, nem automatikus tick egységeket
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; // c:dateAx-t ír
Axis.BaseTimeUnit := xlsxChartTimeDays; // a BaseTimeUnitSet-et is beállítja
Axis.MajorTimeUnit := xlsxChartTimeMonths;
Axis.MajorUnit := 1; // > 0 a majorUnit beállítottnak jelzése
Axis.NumberFormat := 'mmm yyyy';
Axis.NumberFormatSourceLinked := False;
Axis.TextStyle.Rotation := -45; // fokban; tárolva 1/60000-ként
// Az AutoDateAxis érintetlen: nem íródik c:auto elem
Book.SaveAs('month-end-balance.xlsx');
finally
Book.Free;
end;
end;
A címkék és a tájolás ugyanazokat a jelenlét-tudatos szabályokat követik. A tengely numFmt-je a cím után és a tick jelek előtt ül, a sourceLinked attribútuma alapból true, a TXLSXChartAxis pedig külön tartja a formátumkódot, a source-linked értéket és a NumberFormatSet-et, így egy üres kódot soha nem tévesztenek össze hiányzó elemmel. A szövegforgatás a fok 1/60000-edében tárolódik, a pozitív érték az óramutató járásával egyező irányt jelent; a TextStyle.Rotation sima fokot vesz, az SVG kimenet a szöget úgy használja, ahogy van, a lapozott backend, amelynek pozitív szögei az óramutató járásával ellentétesek, egyetlen közös határon váltja az előjelet. Egy fordított maxMin tengely egyszerre tükrözi a major és minor tick-eket, a rácsvonalakat, az adatpontokat, a trendvonalakat és a hibasávokat, soha nem csak a címkéket. Amikor máshol írt munkafüzetet nyit meg, a jelzők megmondják, mit adott meg ténylegesen a szerző
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; // mentse el újra, és az elhagyott elemek elhagyottak maradnak
end;
end;
Hol ér véget a naptári modell?
A naptári modell olyan formátumoknál ér véget, amelyek nem tudnak naptári egységeket kifejezni, és a HotXLS nem színleli, hogy másképp lenne. Az ODF 1.2 kínál chart:interval-major-t, amely sima szám, napokról, hónapokról, évekről nincs fogalma, tehát egy ODS-ként mentett Excel havi tengely nem hordozhatja az egységét, és a HotXLS nem talál ki nem szabványos attribútumot, hogy színleljen egyet. A legacy BIFF8 oldalon az AxcExt rekord kilenc rögzített 16 bites szót tart, és az automatikus jelzői csupán maszkolják a mezőket, nem távolítják el őket; a HotXLS a maszkolt minimumot, maximumot, intervallumot, egységet és metszéspontot megőrzi olvasáskor és visszaíráskor, az ismeretlen egységkódokat érintetlenül tartja a Classic modellben, és csak az érvényes dátumegységeket, a 0, 1 és 2 értékeket (nap, hónap, év) képezi le XLSX-re
A dátumtengelyek annak a diagrammodellnek az egyik rétege, amelynek az általa nem készített fájlokat is túl kellett élnie. Amikor egy Excel munkafüzet egy dátumtengelyes vonalat kombinál egy oszlopsorozattal másodlagos tengelyen, a megőrzött ChartML-ről és a kombinációs diagramokról szóló cikkünk megmutatja, hogyan játssza vissza a HotXLS az eredeti chart XML-t bájtról bájtra, ha semmi nem változott, és hogyan fésüli bele a típusos szerkesztéseket, ha valami mégis, a diagramokról, képekről és rajzokról szóló áttekintésünk pedig fedeli a fent használt horgony- és sorozat-API-kat. Mindez a HotXLS Delphi Component csomagban érkezik Delphihez és C++Builderhez, amely XLS és XLSX diagramokat olvas, ír és rajzol Excel automatizálás nélkül