HotXLS Excel diagramų datų ašis atvaizduoja kalendoriaus aritmetika, o ne fiksuotais dienų skaičiais. TXLSChartDateAxisTransform klasė lxChart modulyje kiekvieną koordinatę laiko darbaknygės serija, kiekvieną mėnesio ar metų žymeklį apskaičiuoja tiesiai iš ašies minimumo per IncMonth ir IncYear ir mėnesio galus suvaržo prie tikro kalendoriaus, tad serija, pririšta prie sausio 31, žymeklius gauna vasario 28, kovo 31 ir balandžio 30, užuot slysdama. Tas pats transformatorius automatiškai parinka dienų, mėnesių ar metų vienetus ir yra bendras HTML, SVG ir puslapiuoto PDF diagramų atvaizduokliams
Defektas, kurį tai apsaugo, pažįstamas kiekvienam, kas yra braižęs mėnesio pabaigos balansą. Atvaizduoklis, laikantis mėnesį 30 dienų, per pirmuosius metus nuslysta penkiom dienom ankčiau. Atvaizduoklis, gudresnis ir naudojantis kalendoriaus mėnesius, bet žymeklį žengiantis nuo ankstesniojo, klysta tyliau: sausio 31 tampa vasario 28, kitas žingsnis nusileidžia kovo 28, ir visi paskesni žymekliai lieka prikibę prie 28-os. Diagrama atrodo tikėtina, užrašai klaidingi, ir niekas nepastebi, kol buhalteris neklausia, kodėl kovo balansas nubraižytas trimis dienom anksčiau ketvirčio pabaigos
Kodėl mėnesių žymekliai nuslysta, kai jie kaupiami?
Mėnesių žymekliai nuslysta, nes suvaržymas išmeta informaciją. Kai sausio 31 suvaržytas į vasario 28, fakto, kad serija norėjo 31-os, nebėra, ir bet koks žingsnis nuo suvaržytos reikšmės paveldi tą nuostolį. TXLSChartDateAxisTransform.BuildTicks niekada nežengia nuo ankstesnio žymeklio. Žymeklis i visada skaičiuojamas kaip AddUnits(MinValue, UnitKind, Step * i, ...), tai yra min + vienetas × indeksas, matuojama nuo ašies minimumo. AddUnits dienų vienetams skaičių prideda tiesiai prie serijos, o mėnesių ir metų vienetams seriją paverčia tikra data, kviečia IncMonth arba IncYear ir konvertuoja atgal. Ciklas ir apribotas iš visų pusių: rezultatų masyvas ribojamas 4096 įrašų, ko besprašytų kvietėjas, Step * i patikrinamas prieš MaxInt dar prieš dauginant, o generavimas stabdomas vos žymekliui nustojus didėti ar praėjus ašies maksimumą
uses
SysUtils, lxChart;
var
Ticks: TXLSChartDateValues;
MinSerial, MaxSerial: Double;
I, Count: Integer;
begin
// 1900 data sistema (Dates1904 = False), mėnesio galu pririšta serija
MinSerial := TXLSChartDateAxisTransform.DateTimeToSerial(
EncodeDate(2026, 1, 31), False);
MaxSerial := TXLSChartDateAxisTransform.DateTimeToSerial(
EncodeDate(2026, 12, 31), False);
// Vieno mėnesio žingsnis, siekiama 12 žymeklių, daugiau nei 64 — niekada
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;
BuildTicks turi vieną sąžiningą aproksimaciją. Kai prašomas žingsnis nulinis, metodas žingsnio dydį įvertina laikydamas mėnesį 30 dienų, o metus 365, tada žaliąjį žingsnį suapvalina iki 1, 2 arba 5 dešimt laipsnio kartotinio. Tas įvertinimas nusprendžia tik tai, kiek vienetų telpa tarp žymeklių. Patys žymeklių taškai vis tiek ateina iš IncMonth ir IncYear, tad aproksimacija gali pakeisti žymeklių tankį, bet niekada nuverčia žymeklio nuo kalendoriaus
Kodėl laikyti darbaknygės serijas, užuot konvertavus į TDateTime?
Datų ašis HotXLS koordinačių erdvei laiko darbaknygės serijas, nes Delphi TDateTime neturi vietos 60-ajai serijai — fantominėms 1900 metų vasario 29 dienoms, kurias 1900 data sistema paveldėjo kaip suderinamumo ydą. TrySerialToDateTime serijas žemiau 60 paslenka diena, o 60-ąją sulieja su vasario 28 — vienam taškui užrašyti tai gerai, geometrijai mirtina: konvertavęs kiekvieną tašką į TDateTime pirmiausia gausi, kad dvi dienos abi fantominės dienos pusės atsiduria vienu vienetu arčiau viena kitos, nei Excel jas piešia, tad viskas, nubraižyta iki 1900 metų kovo, pasislenka viso kito atžvilgiu. TXLSChartDateAxisTransform.FormatValue tą pačią vietą apdoroja išimtine tvarka ir užrašo 1900-02-29 lygiai kaip Excel. 1904 darbaknygėje 0-oji serija yra 1904 metų sausio 1, o transformatorius abejomis kryptimis taiko fiksuotą 1462 dienų poslinkį; visa abiejų epochų istorija — mūsų straipsnyje apie Excel datų serijas, 1904 sistemą ir skaičių formatus
Kaip HotXLS automatiškai parinka dienas, mėnesius ar metus?
TXLSChartDateAxisTransform.DetectUnit pasirenka smulkiausią kalendoriaus vienetą, kurio duomenų tarpams iš tikrųjų reikia. Metodas pirmiausia išmeta NaN ir begalines reikšmes, likusias vieną kartą surūšiuoja O(n log n) ir praleidžia pasikartojančias datas. Jei kuris nors gretimų tarpas trumpesnis už vieną kalendoriaus mėnesį, vienetas — dienos; kitu atveju, jei kuris tarpas trumpesnis už kalendoriaus metus, — mėnesiai; dar kitu atveju — metai. Tikrinama IncMonth(Previous, 1) > Current, o ne 30 dienų slenkstis, tad mėnesio pabaigos serija sausio 31, vasario 28, kovo 31 teisingai atpažįstama kaip mėnesiniai duomenys. Kai skirtingų teisėtų datų mažiau nei dvi, matuoti nėra ko, ir metodas nusileidžia į dienas
ResolveUnits tada sujungia aptiktą vienetą su tuo, ką deklaruoja diagrama. Aiškiai nustatytas bazinis vienetas gerbiamas, o trūkstamas pagrindinis ar šalutinis vienetas paveldi aptiktąjį, kai šis šiurkštesnis už bazinį. Paskutinis žingsnis — tas, kurio žmonės nepastebi: jei efektyvusis pagrindinis ar šalutinis vienetas smulkesnis už bazinį, bazinis nuleidžiamas, kad sutaptų. Bazinis vienetas yra tai, į ką duomenų taškai normalizuojami, dar neegzistuojant jokiems žymekliams: Normalize reikšmę prisega prie jos dienos, mėnesio pirmosios ar sausio 1, tad mėnesinis bazinis po dieniniu pagrindiniu vienetu mėnesio taškus suspaustų į vieną vietą, dar žymeklių kodui jų nepamatius. Žymeklių tankis irgi ribojamas — braižymo ilgiu, išmatuotu ašies šrifto pločiumi, užrašų skaičių formatu ir pasuktų užrašų projekcija, — ir kiekvienas backend braižo užrašus, žymeklių brūkšnius ir tinklelius iš vieno bendro, riboto žymeklių masyvo, o šalutinis žymeklis, sutampantis su pagrindiniu, braižomas tik kartą
Ką saugo dateAx elementas ir ką reiškia praleidimas?
ChartML kiekvienas datų ašies kalendoriaus nustatymas yra pasirenkamas, ir jo praleidimas nėra tas pats, kas numatytosios reikšmės užrašymas. CT_DateAx tipas ECMA-376 1 dalyje, §21.2 (DrawingML Charts) leidžia baseTimeUnit, majorTimeUnit, minorTimeUnit ir auto kiekvienam pasirodyti ar nepasirodyti, ir praleistas baseTimeUnit sako Exceliui spręsti pačiam — tai kita, nei aiškiai parašyta days. TXLSXChartAxis todėl kiekvieną reikšmę saugo šalia buvimo žymės: BaseTimeUnit su BaseTimeUnitSet, MajorTimeUnit su MajorTimeUnitSet, MinorTimeUnit su MinorTimeUnitSet ir AutoDateAxis su AutoDateAxisSet. Reikšmės priskyrimas nustato žymę, žymės išvalymas grąžina elementą į praleistą būseną, o XLSX rašytojas elementą išveda tik tada, kai žymė nustatyta. Atkreipkite dėmesį, kad auto reiškia automatinę kategorijos ir datos atskyrimo detekciją, o ne automatinius žymeklių vienetus
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; // rašo c:dateAx
Axis.BaseTimeUnit := xlsxChartTimeDays; // taip pat nustato BaseTimeUnitSet
Axis.MajorTimeUnit := xlsxChartTimeMonths;
Axis.MajorUnit := 1; // > 0 pažymi majorUnit kaip nustatytą
Axis.NumberFormat := 'mmm yyyy';
Axis.NumberFormatSourceLinked := False;
Axis.TextStyle.Rotation := -45; // laipsniais; saugoma kaip 1/60000
// AutoDateAxis nelietas: jokio c:auto elemento nerašoma
Book.SaveAs('month-end-balance.xlsx');
finally
Book.Free;
end;
end;
Užrašai ir orientacija laikosi tų pačių buvimą atmenančių taisyklių. Ašies numFmt stovi po pavadinimu ir prieš žymeklių brūkšnius, jo sourceLinked atributas pagal nutylėjimą true, o TXLSXChartAxis formatų kodą, source-linked reikšmę ir NumberFormatSet laiko atskirai, tad tuščias kodas niekada nepainiojamas su nebūtu elementu. Teksto pasukimas saugomas 1/60000 laipsnio, teigiamos reikšmės reiškia pagal laikrodžio rodyklę; TextStyle.Rotation ima paprastus laipsnius, SVG išvestis kampą naudoja tokį, koks yra, o puslapiuotas backend, kurio teigiami kampai eina prieš laikrodžio rodyklę, ženklelį apverčia vienoje bendroje riboje. Apversta maxMin ašis veidrodiškai atspindėja pagrindinius ir šalutinius žymeklius, tinklelius, duomenų taškus, trendlines ir error bars kartu, ir niekada vien tik užrašus. Atidarę kitur rašytą darbaknygę, iš žymių sužinosite, ką autorius iš tikrųjų nustatė
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; // įrašius dar kartą praleisti elementai lieka praleisti
end;
end;
Kur sustoja kalendoriaus modelis?
Kalendoriaus modelis sustoja prie formatų, nepajėgių išreikšti kalendoriaus vienetų, ir HotXLS to nesisvajoja. ODF 1.2 turi chart:interval-major — paprastą skaičių be jokios sampratos apie dienas, mėnesius ar metus, tad Excelio mėnesinė ašis, išsaugota kaip ODS, negali nešti savo vieneto, o HotXLS neišranda nestandartinio atributo jo apsimesti. Senojo BIFF8 pusėje AxcExt įrašas laiko devynis fiksuotus 16 bitų žodžius, o jo automatinės žymės tik uždengia laukus, nepašalindamos jų; HotXLS uždengtąjį minimumą, maksimumą, intervalą, vienetą ir kirtimo reikšmes išlaiko skaitant ir rašant atgal, nežinomus vienetų kodus Classic modelyje palieka nepaliestus ir į XLSX atvaizduoja tik teisėtus datų vienetus 0, 1 ir 2 (dienos, mėnesiai, metai)
Datų ašys yra vienas diagramos modelio sluoksnis, kuris dar turi išgyventi failus, kurių pats nesukūrė. Kai Excel darbaknygė jungia datų ašies liniją su stulpelių serija antrinėje ašyje, mūsų straipsnis apie išsaugomą ChartML ir kombinuotas diagramas rodo, kaip HotXLS originalų diagramos XML pakartoja baitas į baitą, kai niekas nepasikeitė, ir į jį suslieja tipizuotus redagavimus, kai kas pasikeitė, o mūsų diagramų, vaizdų ir piešinių apžvalga dengia aukščiau naudotus inkaravimo ir serijų API. Visa tai atkeliauja su HotXLS Delphi Component Delphi ir C++Builder, skaitančiu, rašančiu ir atvaizduojančiu XLS bei XLSX diagramas be Excel automatizavimo