Tehnični članak

Datumska os grafikona v HotXLS: črtice točne na koledar

HotXLS izrisuje datumske osi grafikonov Excel s koledarsko aritmetiko in ne s fiksnim številom dni. Razred TXLSChartDateAxisTransform v enoti lxChart vsako koordinato vodi kot serijsko vrednost delovnega zvezka, vsako mesečno ali letno črtico izračuna neposredno iz minimuma osi prek IncMonth in IncYear ter konce mesecev stisne na pravi koledar, tako da serija, zasidrana na 31. januar, dobi črtice 28. februarja, 31. marca in 30. aprila, namesto da bi drsela. Isti transform samodejno izbere enote za dan, mesec ali leto in ga si delijo izrisovalniki grafikonov HTML, SVG in paginiran PDF

Hrošč, ki mu to prepreči, je znan vsakomur, ki je že grafikonal zaključek meseca. Izrisovalnik, ki mesec obravnava kot 30 dni, se do konca prvega leta zdrsne pet dni prezgodaj. Izrisovalnik, ki je pametnejši in uporablja koledarske mesece, vsako črtico pa pomika od prejšnje, odpove tišje: 31. januar postane 28. februar, naslednji korak pristane na 28. marca, vse črtice za tem pa ostanejo pripete na 28. Grafikon izgleda verjeten, oznake so napačne in nihče ne opazi, dokler kontrolor ne vpraša, zakaj je marčevsko stanje narisano tri dni pred koncem četrtletja

Zakaj mesečne črtice pri kopičenju drsijo?

Mesečne črtice drsijo, ker stiskanje v meje odvrže informacijo. Ko je 31. januar enkrat stisnjen na 28. februar, je dejstvo, da je serija hotela 31., izgubljeno, vsak korak, narejen iz stisnjene vrednosti, pa izgubo podeduje. TXLSChartDateAxisTransform.BuildTicks nikoli ne koraka od prejšnje črtice. Črtica i je vedno izračunana kot AddUnits(MinValue, UnitKind, Step * i, ...), kar je min + enota × indeks, merjeno od minimuma osi. AddUnits števec doda neposredno serijski vrednosti za enote dni, za enote mesecev in let pa serijsko vrednost pretvori v pravi datum, pokliče IncMonth ali IncYear in pretvori nazaj. Zanka je omejena tudi s vseh strani: tabelo rezultatov omeji na 4096 vnosov, ne glede na to, kaj klicnik zahteva, Step * i se preveri proti MaxInt, še preden se zmnoži, in ustvarjanje se ustavi, takoj ko črtica ne zraste ali preide maksimum osi

Dva načina, kako lahko datumska os grafikona HotXLS namesti mesečne črtice po zasidranju na 31. januar 2026: pomikanje vsake črtice od prejšnje stisnjene vrednosti ostane pripeto na 28., BuildTicks pa izračuna AddUnits(MinValue, xcduMonths, Step krat i) iz minimuma osi, tako da črtice pristanejo 28. februarja, 31. marca in 30. aprila
Stiskanje v meje odvrže informacijo, zato TXLSChartDateAxisTransform nikoli ne koraka od prejšnje črtice in znova izračuna vsak položaj iz minimuma osi, konce mesecev pa stisne na pravi koledar
uses
  SysUtils, lxChart;

var
  Ticks: TXLSChartDateValues;
  MinSerial, MaxSerial: Double;
  I, Count: Integer;
begin
  // datumski sistem 1900 (Dates1904 = False), serija zasidrana na konec meseca
  MinSerial := TXLSChartDateAxisTransform.DateTimeToSerial(
    EncodeDate(2026, 1, 31), False);
  MaxSerial := TXLSChartDateAxisTransform.DateTimeToSerial(
    EncodeDate(2026, 12, 31), False);

  // korak enega meseca, cilj 12 črtic, nikoli več kot 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 en iskren približek. Ko je zahtevani korak nič, metoda oceni velikost koraka tako, da mesec obravnava kot 30 dni in leto kot 365, surovi korak pa zaokroži na 1-, 2- ali 5-kratnik desetiške potence. Ta ocena odloča le o tem, koliko enot leži med črticami. Položaji črtic sami še vedno prihajajo iz IncMonth in IncYear, zato približek lahko spremeni gostoto črtic, črtice pa nikoli ne spravi s koledarja

Zakaj obdržati serijske vrednosti delovnega zvezka namesto pretvorbe v TDateTime?

Datumska os v HotXLS obdrži serijske vrednosti delovnega zvezka kot svoj koordinatni prostor, ker Delphijev TDateTime nima reže za serijo 60, fantomski 29. februar 1900, ki ga je datumski sistem 1900 podedoval kot čudažnost za združljivost. TrySerialToDateTime serijske vrednosti pod 60 premakne za en dan in serijo 60 zloži na 28. februar, kar je v redu za označevanje posamezne točke, geometriji pa usodno: če najprej pretvorite vsako točko v TDateTime, sta dva dneva ob fantomskem dnevu za eno enoto bliže kot ju nariše Excel, zato se vse, narisano pred marcem 1900, premakne glede na vse za njim. TXLSChartDateAxisTransform.FormatValue isto režo obravnava posebej in jo označi kot 1900-02-29, točno tako kot Excel. V delovnem zvezku 1904 je serija 0 datum 1. januar 1904, transform pa v obe smeri uporabi fiksni odmik 1462 dni; celotno zgodbo obeh epoh pripoveduje naš članek o datumskih serijskih vrednostih Excel, sistemu 1904 in številskih formatih

Zakaj datumska os HotXLS obdrži serijske vrednosti delovnega zvezka namesto pretvorbe v TDateTime: serija 60 je fantomski 29. februar 1900, TrySerialToDateTime serijske vrednosti pod 60 premakne in 60 zloži na 28. februar, tako da sta dneva ob njej za eno enoto bliže kot ju nariše Excel, FormatValue pa serijo 60 označi kot 1900-02-29
Zložitev je v redu za označevanje posamezne točke, geometriji pa usodna, ker se vse, narisano pred marcem 1900, premakne glede na vse za njim; delovni zvezek 1904 namesto tega uporabi fiksni odmik 1462 dni

Kako HotXLS samodejno izbere dneve, mesece ali leta?

TXLSChartDateAxisTransform.DetectUnit izbere najbolj fino koledarsko enoto, ki jo vrzeli v podatkih dejansko potrebujejo. Metoda najprej odvrže vrednosti NaN in neskončne, preostale enkrat uredi v O(n log n) in preskoči podvojene datume. Če je katera koli sosednja vrzel krajša od enega koledarskega meseca, je enota dni; sicer, če je katera koli vrzel krajša od enega koledarskega leta, je enota mesecev; sicer pa let. Preizkus je IncMonth(Previous, 1) > Current in ne prag 30 dni, zato je serija koncev mesecev 31. januar, 28. februar, 31. marec pravilno prepoznana kot mesečni podatki. Z manj kot dvema različnima veljavnima datumoma ni vrzeli za merjenje in metoda pade nazaj na dneve

ResolveUnits nato prepozvano enoto spoji s tem, kar napove grafikon. Izrecna osnovna enota je spoštovana, manjkajoča glavna ali manjša enota pa podeduje prepozvano, kadar je ta grobejša od osnovne. Zadnji korak je tisti, ki ga ljudje spregledajo: če je dejavna glavna ali manjša enota finejša od osnovne enote, se osnovna zniža, da se prilagodi. Osnovna enota je tisto, na kar se točke podatkov normalizirajo, še preden sploh obstajajo črtice, Normalize pa vrednost pripne na njen dan, prvi dan njenega meseca ali 1. januar, zato bi mesečna osnova pod dnevno glavno enoto zrušila mesec točk v eno režo, še predno bi jih koda za črtice sploh videla. Tudi gostota črtic je omejena — z dolžino risbe, izmerjeno širino pisave osi, številskim formatom oznak in projekcijo zasukanih oznak — in vsak zaledni sistem riše oznake, črtice in črte mreže iz ene skupne, omejene tabele črtic, pri čemer se manjša črtica, ki sovpada z glavno, nariše le enkrat

Kako HotXLS samodejno izbere enote datumske osi: DetectUnit odvrže vrednosti NaN in neskončne, enkrat uredi in koledarske vrzeli meri z IncMonth in ne s pragom 30 dni, zato je serija 31. januar, 28. februar, 31. marec prepoznana kot mesečna, ResolveUnits pa spoštuje izrecno osnovo in jo zniža, kadar je glavna enota finejša
Normalize vsako vrednost pripne na njen dan, prvi dan njenega meseca ali 1. januar, še preden sploh obstajajo črtice, ker bi mesečna osnova pod dnevno glavno enoto zrušila mesec točk v eno režo

Kaj shrani element dateAx in kaj pomeni izpuščenost?

V ChartML je vsaka koledarska nastavitev na datumski osi neobvezna in izpustiti eno ni isto kot zapisati njeno privzeto vrednost. Tip CT_DateAx v ECMA-376 Part 1, §21.2 (DrawingML Charts) dovoljuje, da se baseTimeUnit, majorTimeUnit, minorTimeUnit in auto vsak pokaže ali ne, izpuščeni baseTimeUnit pa pove Excelu, naj se odloči sam — kar je drugače kot izrecni days. TXLSXChartAxis zato vsako vrednost shrani ob zastavici prisotnosti: BaseTimeUnit z BaseTimeUnitSet, MajorTimeUnit z MajorTimeUnitSet, MinorTimeUnit z MinorTimeUnitSet in AutoDateAxis z AutoDateAxisSet. Dodelitev vrednosti nastavi njeno zastavico, počistitev zastavice vrne element v izpuščenost, pisec XLSX pa emitira element le, kadar je njegova zastavica nastavljena. Upoštevajte, da auto pomeni samodejno zaznavanje kategorij ali datumov in ne samodejne enote črtic

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;                  // zapiše c:dateAx
    Axis.BaseTimeUnit := xlsxChartTimeDays;     // hkrati nastavi BaseTimeUnitSet
    Axis.MajorTimeUnit := xlsxChartTimeMonths;
    Axis.MajorUnit := 1;                        // > 0 označi majorUnit kot nastavljen
    Axis.NumberFormat := 'mmm yyyy';
    Axis.NumberFormatSourceLinked := False;
    Axis.TextStyle.Rotation := -45;             // stopinje; shranjeno kot 1/60000
    // AutoDateAxis nedotaknjen: element c:auto se ne zapiše

    Book.SaveAs('month-end-balance.xlsx');
  finally
    Book.Free;
  end;
end;

Oznake in usmerjenost sledijo istim pravilom, ki poznajo prisotnost. numFmt osi sede za naslovom in pred črticami, njegov atribut sourceLinked ima privzeto true, TXLSXChartAxis pa obdrži kodo formata, vrednost povezave z virom in NumberFormatSet ločeno, zato prazne kode nikoli ne zamenja z odsotnim elementom. Zasuk besedila je shranjen v 1/60000 stopinje, pozitivne vrednosti pa pomenijo smer urnega kazalca; TextStyle.Rotation sprejme navadne stopinje, izhod SVG uporabi kot, kot je, paginirana zaledna stran, kjer pozitivni koti tečejo v nasprotni smeri urnega kazalca, pa predznak obrne na eni skupni meji. Obrnjena os maxMin zrcali glavne in manjše črtice, črte mreže, točke podatkov, trendne črte in stolpce napak skupaj in nikoli le oznake. Ko odprete delovni zvezek, napisan drugje, vam zastavice povedo, kaj je avtor dejansko določil

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;   // znova shranite in izpuščeni elementi ostanejo izpuščeni
  end;
end;

Kje se koledarski model ustavi?

Koledarski model se ustavi pri formatih, ki koledarskih enot ne znajo izraziti, in HotXLS se ne dela, da je drugače. ODF 1.2 ponuja chart:interval-major, ki je golo število brez pojma dni, mesecev ali let, zato mesečna os Excel, shranjena kot ODS, ne more nositi svoje enote, HotXLS pa ne izumi nestandardnega atributa, da bi jo pretvarjal. Na strani starejšega BIFF8 zapis AxcExt nosi devet fiksnih 16-bitnih besed, njegove samodejne zastavice pa polja le prekrijejo, namesto da bi jih odstranile; HotXLS prekrite vrednosti minimuma, maksimuma, intervala, enote in presečišča ohrani pri branju in povratnem zapisu, neznane kode enot obdrži nedotaknjene v modelu Classic in v XLSX preslika le veljavne datumske enote 0, 1 in 2 (dnevi, meseci, leta)

Datumski osi so ena plast modela grafikonov, ki mora preživeti tudi datoteke, ki jih ni ustvarila sama. Ko Excelov delovni zvezek združi črto na datumski osi s stolpčno serijo na sekundarni osi, naš članek o ohranjenem ChartML in kombiniranih grafikonih pokaže, kako HotXLS izvirni XML grafikona ponovno predvaja bajt za bajtom, kadar se nič ni spremenilo, in vanj spoji tipizirane spremembe, kadar se je kaj, naš pregled grafikonov, slik in risb pa pokriva API-ja za zasidranje in serije, uporabljena zgoraj. Vse to je del komponente HotXLS Delphi Component za Delphi in C++Builder, ki bere, piše in izrisuje grafikone XLS in XLSX brez avtomatizacije Excel