Een saldochart op maandeeinde die start op 31 januari 2026 hoort zijn april-tick op 30 april te zetten. Elke tick vanuit de vorige opvoeren met kalendermaanden en de labels komen uit als 2026-01-31, 2026-02-28, 2026-03-28, 2026-04-28, met elke latere tick vastgekleefd op de 28e. In plaats daarvan met een vlakke 30 dagen opvoeren en de februari-tick landt op 2 maart, terwijl de december-tick op 27 december landt. De regel die beide valkuilen overleeft is simpel te zeggen: reken elke tick vanuit het minimum van de as, en kap maandeeinden af op de echte kalender
Op een chart voor de kwartaatafsluiting is de eerste vergissing de gevaarlijke, want het plaatje ziet er nog steeds goed uit. De lijn stijgt waar hij hoort, de labels staan gelijkmatig verdeeld, en het maartsaldo zit drie dagen voor het kwartaadeinde. Een controller die de chart naast het grootboek legt is degene die het vindt, en tegen die tijd is de chart meestal al in een bestuursmap geplakt
Waarom schuiven maandticks als ze worden opgestapeld?
Maandticks schuiven omdat afkappen informatie weggooit. Is 31 januari eenmaal afgekapt op 28 februari, dan is het feit dat de serie de 31e wilde weg, en elke stap vanuit de afgekapte waarde erft het verlies. In HotXLS stapt TXLSChartDateAxisTransform.BuildTicks in de unit lxChart nooit vanuit een eerdere tick: tick i is altijd AddUnits(MinValue, UnitKind, Step * i, ...), dus maart is twee maanden na 31 januari en komt uit als 31 maart. AddUnits telt het aantal bij de serial op voor dageenheden; voor maand- en jaareenheden zet hij de serial om in een echte datum, roept IncMonth of IncYear aan, en reist terug
De lus is aan elke kant begrensd. De resultaatarray komt nooit boven 4096 entries uit wat de aanroeper ook als maximum doorgeeft, Step * i wordt tegen MaxInt gecontroleerd voordat de vermenigvuldiging plaatsvindt, en de generatie stopt zodra een tick niet stijgt of voorbij het maximum van de as gaat. Een vijandige werkmap met een span van een paar miljoen jaar levert een korte array op en keert terug
De transformatie is publiek, dus u kunt de rekenkunst zelf draaien voordat er een chart bestaat. Dit consolefragment bouwt een jaar aan maandticks voor de serie hierboven en print de labels die de chart zal gebruiken
uses
SysUtils, lxChart;
var
Ticks: TXLSChartDateValues;
MinSerial, MaxSerial: Double;
I, Count: Integer;
begin
// 1900-datumsysteem (Dates1904 = False), serie verankerd op maandeeinde
MinSerial := TXLSChartDateAxisTransform.DateTimeToSerial(
EncodeDate(2026, 1, 31), False);
MaxSerial := TXLSChartDateAxisTransform.DateTimeToSerial(
EncodeDate(2026, 12, 31), False);
// Stap van één maand, mik op 12 ticks, nooit meer dan 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;
Er zit precies één benadering in BuildTicks, en die zit waar een benadering ongevaarlijk is. Is de gevraagde stap nul, dan schat de methode een stap door een maand als 30 dagen en een jaar als 365 te nemen, en rondt hij de rauwe stap omhoog af op een 1-, 2- of 5-voud van een macht van tien. Die schatting beslist alleen hoeveel eenheden er tussen ticks zitten. Posities komen nog steeds uit IncMonth en IncYear. Een ruwe maandlengte kan de as iets dichter of schaarser maken, maar elke tick landt nog steeds op een datum die een van die twee functies produceerde
Workbook-serials blijven de ascoördinaat
Een HotXLS-datumas houdt workbook-serials als zijn coördinatenruimte omdat de TDateTime van Delphi geen vakje heeft voor serial 60. In het 1900-datumsysteem is die serial 29 februari 1900, een dag die nooit heeft bestaan en die Excel houdt uit compatibiliteit met oudere spreadsheetprogramma's. TXLSChartDateAxisTransform.TrySerialToDateTime behandelt hem op de enige manier die een echte kalender toelaat: serials onder 60 schuiven één dag op en serial 60 vouwt samen op 28 februari. Voor het labelen van één punt is dat prima. Voor geometrie is het fout, want serials 59 en 60 worden dezelfde TDateTime, en 28 februari en 1 maart komen één eenheid uit elkaar waar Excel ze twee uit elkaar tekent
Drie serials tonen beide kanten van het probleem. FormatValue geeft het fantoomvakje een bijzondere behandeling en print exact wat Excel print, terwijl een rondtrip door TDateTime hem verliest
var
S: Integer;
D: TDateTime;
begin
for S := 59 to 61 do
begin
TXLSChartDateAxisTransform.TrySerialToDateTime(S, False, D);
Writeln(S, ' axis label: ',
TXLSChartDateAxisTransform.FormatValue(S, False),
' TDateTime: ', FormatDateTime('yyyy-mm-dd', D));
end;
// 59 axis label: 1900-02-28 TDateTime: 1900-02-28
// 60 axis label: 1900-02-29 TDateTime: 1900-02-28
// 61 axis label: 1900-03-01 TDateTime: 1900-03-01
end;
Haast niemand chart gegevens uit begin 1900, dus de praktische les gaat over uw eigen code. Rekent u asgrenzen of annotatieposities uit, doe het in serials en converteer met DateTimeToSerial aan de rand, zoals het eerste fragment doet. Mengt u een serial-gebaseerde as met TDateTime-rekenkunst in de aanroeper, dan landt een annotatie gedateerd vóór 1 maart 1900 één dag van zijn punt af. Het tweede tijdperk doet zich vaker voor: in een 1904-werkmap is serial 0 1 januari 1904, en de transformatie past de vaste offset van 1462 dagen in beide richtingen toe wanneer het argument Dates1904 waar is. Beide tijdperken, en hoe celgetalnotaties ermee samenspelen, worden behandeld in ons artikel over Excel-datumserials, het 1904-systeem en getalnotaties
Hoe kiest HotXLS automatisch dagen, maanden of jaren?
TXLSChartDateAxisTransform.DetectUnit kiest de fijnste kalendereenheid die de gaten in de data werkelijk nodig hebben. Hij gooit NaN- en oneindige waarden weg, sorteert een kopie van de rest één keer, en slaat dubbele datums over. Is een van de aangrenzende gaten korter dan één kalendermaand, dan is de eenheid dagen; anders, is een gat korter dan een kalenderjaar, dan maanden; anders jaren. De maandtoets is IncMonth(Previous, 1) > Current, en die keuze doet er precies voor de data in dit artikel. Een drempel van 30 dagen zou het gat van 31 januari naar 28 februari "minder dan een maand" noemen en een maandserie naar dagen schakelen. Met minder dan twee verschillende geldige datums is er geen gat om te meten, en de methode geeft dagen terug
ResolveUnits voegt de gedetecteerde eenheid samen met wat de chart verklaart. Een expliciete basiseenheid wint. Een ontbrekende major- of minor-unit erft de gedetecteerde eenheid wanneer die grover is dan de basis, en valt anders terug op de basis. De laatste regel is degene die mensen missen: is de effectieve major- of minor-unit fijner dan de basis, dan zakt de basis mee. De basiseenheid beslist hoe punten worden gegroepeerd voordat er een tick bestaat, want Normalize zet een waarde vast op zijn dag, de eerste van zijn maand of 1 januari. Een maandelijkse basis onder een dagelijkse major-unit zou een hele maand punten in één vak vouwen voordat de tickcode ze ooit zag
Hoeveel automatische major ticks verschijnen hangt van meer af dan het datumbereik. De plotlengte, de gemeten breedte van het asfont, de labelgetalnotatie en de geprojecteerde breedte van gedraaide labels kappen de telling af, dus een smalle chart met lange mmmm yyyy-labels krijgt minder ticks dan een brede met mm/yy. Labels, tickmarkeringen en rasterlijnen worden allemaal uit één tickarray getekend, wat HTML-, SVG- en PDF-uitvoer in overeenstemming houdt, en een minor tick die op een major tick valt wordt één keer getekend
Wat slaat het dateAx-element op, en wat betekent weglaten?
In ChartML is elke kalenderinstelling op een datumas optioneel, en weglaten zegt iets anders dan de default schrijven. Het type CT_DateAx in ECMA-376 Part 1, §21.2 (DrawingML Charts) laat baseTimeUnit, majorTimeUnit, minorTimeUnit en auto elk verschijnen of niet. Een weggelaten baseTimeUnit laat Excel beslissen op het moment van renderen; een expliciete days pindt hem. TXLSXChartAxis houdt elke waarde bij een aanwezigheidsvlag: BaseTimeUnit met BaseTimeUnitSet, MajorTimeUnit met MajorTimeUnitSet, MinorTimeUnit met MinorTimeUnitSet, en AutoDateAxis met AutoDateAxisSet. Een waarde toewijzen zet zijn vlag, de vlag op False zetten brengt het element terug naar weglaten, en de XLSX-schrijver zendt een element alleen uit wanneer zijn vlag aan staat. auto is makkelijk verkeerd te lezen: hij stuurt de automatische categorie-tegen-datumdetectie en heeft niets te maken met automatische tick-eenheden
uses
SysUtils, DateUtils, lxHandleX;
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));
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; // schrijft c:dateAx
Axis.BaseTimeUnit := xlsxChartTimeDays; // zet ook BaseTimeUnitSet
Axis.MajorTimeUnit := xlsxChartTimeMonths;
Axis.MajorUnit := 1; // een waarde > 0 markeert majorUnit als gezet
Axis.NumberFormat := 'mmm yyyy';
Axis.NumberFormatSourceLinked := False;
Axis.TextStyle.Rotation := -45; // graden
// AutoDateAxis met rust gelaten: er wordt geen c:auto-element geschreven
Book.SaveAs('month-end-balance.xlsx');
finally
Book.Free;
end;
end;
De basiseenheid is hier met opzet dagen. Datumseinden op maandeeinde vallen op verschillende dagnummers, en een maandelijkse basis zou elk punt vastzetten op de eerste van zijn maand, wat het maartsaldo van 31 maart naar 1 maart verplaatst. Pin de basis op dagen telkens wanneer de exacte dag betekenis draagt, en laat de major-unit de maandelijkse spreiding doen
Labels en oriëntatie volgen dezelfde aanwezigheidsregels. Het numFmt-element van de as heeft default sourceLinked op true, dus TXLSXChartAxis houdt de notatiecode, de source-linked-waarde en NumberFormatSet uit elkaar, en een lege code wordt nooit aangezien voor een ontbrekend element. ChartML bewaart tekstrotatie in 1/60000 van een graad met positieve waarden met de klok mee; TextStyle.Rotation neemt kale graden, en een label op -45 helt in SVG- en PDF-uitvoer dezelfde kant op. Een omgekeerde as (maxMin) spiegelt major- en minor-ticks, rasterlijnen, datapunten, trendlijnen en foutbalken samen. Lezen werkt andersom: open een werkmap die ergens anders is geschreven en de vlaggen vertellen u wat de auteur werkelijk heeft opgegeven
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[1].Charts[0].CategoryAxis; // Sheets[] is 1-based, Charts[] is 0-based
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; // sla hem opnieuw op en de weggelaten elementen blijven weg
end;
end;
Kalendereenheden in ODS en legacy-XLS-bestanden
Het kalendermodel reikt zo ver als elk bestandsformaat het kan uitdrukken. ODF 1.2 beschrijft asspreiding met chart:interval-major, een gewoon getal zonder notie van dagen, maanden of jaren, dus een maandelijkse Excel-as opgeslagen als ODS kan zijn eenheid niet meedragen. HotXLS laat de eenheid weg uit het ODS-bestand in plaats van een niet-standaardattribuut te verzinnen dat andere ODF-applicaties toch zouden negeren. Doet de maandelijkse spreiding ertoe voor de mensen die het bestand openen, stuur dan XLSX mee
BIFF8-werkmappen bewaren datumasinstellingen in het record AxcExt, negen vaste 16-bit woorden. Zijn automatische vlaggen maskeren velden zonder ze te verwijderen, dus een record gemarkeerd "automatisch minimum" draagt nog steeds een minimumwaarde. HotXLS houdt het gemaskeerde minimum, maximum, interval, eenheid en snijpunt bij het lezen en schrijft ze ongewijzigd terug, houdt onbekende unitcodes intact in het XLS-objectmodel, en mapt bij conversie van een chart naar XLSX alleen de geldige datumeenheden 0, 1 en 2 (dagen, maanden, jaren)
Een datumas reist zelden alleen. Koppelt een Excel-werkmap een datumas-lijn aan een kolomserie op een secundaire as, dan legt ons artikel over bewaarde ChartML en combinatiecharts uit hoe HotXLS de oorspronkelijke chart-XML afspeelt wanneer er niets veranderde en getypte bewerkingen ermee samensmelt wanneer er wel iets veranderde, en ons overzicht van charts, afbeeldingen en tekeningen dekt de verankerings- en serieaanroepen die hierboven werden gebruikt. De pagina HotXLS Delphi Component somt de volledige chartfunctieset op voor Delphi en C++Builder, inclusief het lezen, schrijven en renderen van XLS- en XLSX-charts zonder Excel geïnstalleerd