HotXLS renderer Excel chart-datoakser med kalenderaritmetikk, ikke faste dagantall. Klassen TXLSChartDateAxisTransform i uniten lxChart beholder hver koordinat som en arbeidsbokserial, beregner hver måned- eller årstick direkte fra aksens minimum gjennom IncMonth og IncYear, og klemmer månedsslutter til den ekte kalenderen, så en serie forankret på 31. januar får ticks på 28. februar, 31. mars og 30. april i stedet for å skli. Samme transform velger dag-, måned- eller årsenheter automatisk og deles av HTML-, SVG- og de paginerte PDF-chart-rendererne
Feilen dette forhindrer, er kjent for alle som har tegnet en månedsavslutning i et chart. En renderer som behandler en måned som 30 dager, driver fem dager for tidlig ut i det første året. En renderer som er smartere og bruker kalendermåneder, men trinner hver tick fra den forrige, feiler mer stille: 31. januar blir 28. februar, neste trinn lander på 28. mars, og hver tick etter det blir hengende på den 28. Chartet ser plausibelt ut, etikettene er feil, og ingen legger merke til det før en regnskapsfører spør hvorfor saldoen for mars er plottet tre dager før kvartalsavslutningen
Hvorfor driver månedsticks når de akkumuleres?
Månedsticks driver fordi klemming kaster informasjon bort. Så snart 31. januar er klemt til 28. februar, er det faktum at serien ville ha den 31., borte, og ethvert trinn tatt fra den klemte verdien arver tapet. TXLSChartDateAxisTransform.BuildTicks trinner aldri fra en tidligere tick. Tick i beregnes alltid som AddUnits(MinValue, UnitKind, Step * i, ...), som er min + enhet × indeks målt fra aksens minimum. AddUnits legger antallet rett til serialen for dagenheter og, for måned- og årsenheter, konverterer serialen til en ekte dato, kaller IncMonth eller IncYear og konverterer tilbake. Løkken er også avgrenset på alle kanter: resultatmatrisen er begrenset til 4096 oppføringer uansett hva kalleren ber om, Step * i sjekkes mot MaxInt før den multipliseres, og genereringen stopper så snart en tick ikke øker eller passerer aksens maksimum
uses
SysUtils, lxChart;
var
Ticks: TXLSChartDateValues;
MinSerial, MaxSerial: Double;
I, Count: Integer;
begin
// 1900-datosystem (Dates1904 = False), serie forankret på månedsslutt
MinSerial := TXLSChartDateAxisTransform.DateTimeToSerial(
EncodeDate(2026, 1, 31), False);
MaxSerial := TXLSChartDateAxisTransform.DateTimeToSerial(
EncodeDate(2026, 12, 31), False);
// Én måneds steg, sikt etter 12 ticks, aldri mer enn 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;
Det finnes én ærlig approksimasjon i BuildTicks. Når forespurt steg er null, estimerer metoden en steglengde ved å behandle en måned som 30 dager og et år som 365, og runder så det rå steget til et 1-, 2- eller 5-multiplum av en titalls potens. Den estimaten bestemmer bare hvor mange enheter det er mellom ticks. Tickposisjonene selv kommer fortsatt fra IncMonth og IncYear, så approksimasjonen kan endre ticktettheten, men flytter aldri en tick av kalenderen
Hvorfor beholde arbeidsbokserialer i stedet for å konvertere til TDateTime?
En datoakse i HotXLS beholder arbeidsbokserialer som sitt koordinatrom, fordi Delphis TDateTime ikke har noen plass til serial 60, den fantomatiske 29. februar 1900 som 1900-datosystemet arvet som en kompatibilitetskrøll. TrySerialToDateTime forskutter serialer under 60 med én dag og bretter serial 60 inn på 28. februar, som er greit for å merke ett enkelt punkt, men fatalt for geometrien: konverter hvert punkt til TDateTime først, så ender de to dagene på hver side av fantomdagen én enhet nærmere hverandre enn Excel tegner dem, og alt som er plottet før mars 1900 forskutteres i forhold til alt etter. TXLSChartDateAxisTransform.FormatValue behandler samme plass som et spesialtilfelle og merker den 1900-02-29 nøyaktig som Excel gjør. I en 1904-arbeidsbok er serial 0 den 1. januar 1904, og transformen anvender det faste 1462-dagers forskuddet i begge retninger; hele historien om begge epokene finnes i artikkelen vår om Excel-datoserialer, 1904-systemet og tallformater
Hvordan velger HotXLS dager, måneder eller år automatisk?
TXLSChartDateAxisTransform.DetectUnit velger den fineste kalenderenheten som gapene i dataene faktisk trenger. Metoden forkaster NaN- og uendelige verdier først, sorterer resten én gang i O(n log n) og hopper over duplikatdatoer. Er et av de tilstøtende gapene kortere enn én kalendermåned, er enheten dager; ellers, er et gap kortere enn ett kalenderår, er enheten måneder; ellers er det år. Testen er IncMonth(Previous, 1) > Current, ikke en 30-dagers terskel, så en månedsslutteserie som går 31. januar, 28. februar, 31. mars, oppdages korrekt som månedlige data. Med færre enn to distinkte gyldige datoer er det ingen gap å måle, og metoden faller tilbake til dager
ResolveUnits fletter så den oppdagede enheten sammen med det chartet deklarerer. En eksplisitt basisenhet æres, og en manglende hoved- eller underenhet arver den oppdagede enheten når den er grovere enn basen. Siste steg er det folk bommer på: er den effektive hoved- eller underenheten finere enn basisenheten, senkes basen for å matche. Basisenheten er det datapunktene normaliseres til før noen ticks finnes, med Normalize som fester en verdi til sin dag, den første i måneden sin eller 1. januar, så en månedlig base under en daglig hovedenhet ville kollapse en måneds punkter inn i én plass før tick-koden i det hele tatt så dem. Ticktettheten er også begrenset, av plotlengden, den målte bredden av aksefonten, etiketttallformatet og projeksjonen av roterte etiketter, og hver backend tegner etiketter, tick-merker og rutenettlinjer fra én delt, begrenset tick-matrise, med en undertick som sammenfaller med en hovedtick tegnet bare én gang
Hva lagrer dateAx-elementet, og hva betyr utelatelse?
I ChartML er hver kalenderinnstilling på en datoakse valgfri, og å utelate én er ikke det samme som å skrive standardverdien dens. Typen CT_DateAx i ECMA-376 Part 1, §21.2 (DrawingML Charts) lar baseTimeUnit, majorTimeUnit, minorTimeUnit og auto opptre eller ikke, og en utelatt baseTimeUnit ber Excel bestemme selv, noe som er forskjellig fra en eksplisitt days. TXLSXChartAxis lagrer derfor hver verdi ved siden av et tilstedeværelsesflagg: BaseTimeUnit med BaseTimeUnitSet, MajorTimeUnit med MajorTimeUnitSet, MinorTimeUnit med MinorTimeUnitSet og AutoDateAxis med AutoDateAxisSet. Å tildele en verdi setter flagget, å nullstille flagget returnerer elementet til utelatelse, og XLSX-skriveren emitterer et element bare når flagget er satt. Merk at auto betyr automatisk kategori-versus-dato-deteksjon, ikke automatiske tickenheter
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; // skriver c:dateAx
Axis.BaseTimeUnit := xlsxChartTimeDays; // setter også BaseTimeUnitSet
Axis.MajorTimeUnit := xlsxChartTimeMonths;
Axis.MajorUnit := 1; // > 0 markerer majorUnit som satt
Axis.NumberFormat := 'mmm yyyy';
Axis.NumberFormatSourceLinked := False;
Axis.TextStyle.Rotation := -45; // grader; lagres som 1/60000
// AutoDateAxis står urørt: ingen c:auto-elementer skrives
Book.SaveAs('month-end-balance.xlsx');
finally
Book.Free;
end;
end;
Etiketter og orientering følger de samme tilstedeværelsesbevisste reglene. Aksens numFmt sitter etter tittelen og før tick-merkene, attributten sourceLinked står som standard til true, og TXLSXChartAxis holder formatkoden, sourceLinked-verdien og NumberFormatSet separat, så en tom kode blir aldri tatt for et fraværende element. Tekstrotasjon lagres i 1/60000 av en grad med positive verdier som betyr med klokken; TextStyle.Rotation tar rene grader, SVG-outputen bruker vinkelen som den er, og den paginerte backenden, hvis positive vinkler går mot klokken, snur fortegnet ved én delt grense. En reversert maxMin-akse speiler hoved- og underticks, rutenettlinjer, datapunkter, trendlinjer og feilindikatorer sammen, og aldri bare etikettene. Åpner du en arbeidsbok skrevet et annet sted, forteller flaggene deg hva forfatteren faktisk spesifiserte
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; // lagre den igjen, og de utelatte elementene forblir utelatt
end;
end;
Hvor stopper kalendermodellen?
Kalendermodellen stopper ved formater som ikke kan uttrykke kalenderenheter, og HotXLS later ikke som noe annet. ODF 1.2 tilbyr chart:interval-major, som er et bart tall uten forestilling om dager, måneder eller år, så en månedlig Excel-akse lagret som ODS kan ikke bære enheten sin, og HotXLS finner ikke opp et ikke-standardisert attributt for å late som den har én. På legacy BIFF8-siden holder AxcExt-recorden ni faste 16-bit ord, og de automatiske flaggene dens maskerer bare felt i stedet for å fjerne dem; HotXLS bevarer de maskerte minimums-, maksimums-, intervall-, enhets- og krysningsverdiene ved lesing og tilbakeskriving, beholder ukjente enhetskoder intakte i Classic-modellen og mapper bare de gyldige datosenhetene 0, 1 og 2 (dager, måneder, år) til XLSX
Datoakser er ett lag i en chart-modell som også må overleve filer den ikke selv har laget. Kombinerer en Excel-arbeidsbok en datoakse-linje med en kolonneserie på en sekundærakse, viser artikkelen vår om bevart ChartML og kombinasjonscharts hvordan HotXLS spiller av den opprinnelige chart-XML-en byte for byte da ingenting var endret og fletter typede redigeringer inn i den da noe var det, og oversikten vår over charts, bilder og tegninger dekker forankrings- og serie-API-ene brukt over. Alt dette følger med i HotXLS Delphi Component for Delphi og C++Builder, som leser, skriver og renderer XLS- og XLSX-charts uten Excel-automatisering