Teknik Makale

HotXLS Grafik Tarih Ekseni: Takvime Sadık Tick'ler

HotXLS, Excel grafik tarih eksenlerini sabit gün sayılarıyla değil takvim aritmetiğiyle render eder. lxChart unit'indeki TXLSChartDateAxisTransform sınıfı her koordinatı çalışma kitabı serial değeri olarak tutar, her ay ya da yıl tick'ini doğrudan eksen minimumundan IncMonth ve IncYear üzerinden hesaplar ve ay sonlarını gerçek takvime sıkıştırır; böylece 31 Ocak'a demirlemiş bir seri, kayarak ilerlemek yerine 28 Şubat, 31 Mart ve 30 Nisan'da tick alır. Aynı transform gün, ay ya da yıl birimlerini otomatik seçer ve HTML, SVG ile sayfalanmış PDF grafik renderer'ları tarafından paylaşılır

Bunun önlediği hata, ay sonu kapanışı grafiklemiş herkese tanıdıktır. Bir ayı 30 gün sayan bir renderer ilk yılın sonunda beş gün erken kayar. Daha akıllı olup takvim aylarını kullanan ama her tick'i öncekinden ilerleten bir renderer ise daha sessiz başarısız olur: 31 Ocak 28 Şubat'a döner, sonraki adım 28 Mart'a düşer ve ondan sonraki her tick 28'e çakılı kalır. Grafik inandırıcı görünür, etiketler yanlıştır ve mali işler sorumlusu Mart bakiyesinin çeyrek sonundan üç gün önce çizildiğini sorana kadar kimse fark etmez

Ay tick'leri biriktirildiklerinde neden kayar?

Ay tick'leri kayar çünkü sıkıştırma bilgi çöpe atar. 31 Ocak bir kez 28 Şubat'a sıkıştırıldığında serinin 31'i istediği gerçeği kaybolur ve sıkıştırılmış değerden atılan her adım bu kaybı devralır. TXLSChartDateAxisTransform.BuildTicks hiçbir zaman önceki bir tick'ten ilerlemez. i numaralı tick her zaman AddUnits(MinValue, UnitKind, Step * i, ...) olarak hesaplanır; bu, eksen minimumundan ölçülen min + birim × indekstir. AddUnits gün birimleri için sayacı doğrudan serial değerine ekler, ay ve yıl birimleri için ise serial değeri gerçek bir tarihe çevirir, IncMonth ya da IncYear çağırır ve geri çevirir. Döngü her yönden sınırlıdır: sonuç dizisi arayan ne isterse istesin 4096 girdide tavanlanır, Step * i çarpılmadan önce MaxInt'e karşı kontrol edilir ve bir tick artmayı başaramaz ya da eksen maksimumunu geçer geçmez üretim durur

HotXLS grafik tarih ekseninin 31 Ocak 2026 çapasından sonra ay tick'lerini yerleştirmesinin iki yolu: her tick'i önceki sıkıştırılmış değerden ilerletmek 28'e çakılı kalır, BuildTicks ise AddUnits(MinValue, xcduMonths, Step kere i) hesabını eksen minimumundan yaptığı için tick'ler 28 Şubat, 31 Mart ve 30 Nisan'a düşer
Sıkıştırma bilgi çöpe attığı için TXLSChartDateAxisTransform hiçbir zaman önceki tick'ten ilerlemez ve her konumu eksen minimumundan yeniden hesaplayıp ay sonlarını gerçek takvime sıkıştırır
uses
  SysUtils, lxChart;

var
  Ticks: TXLSChartDateValues;
  MinSerial, MaxSerial: Double;
  I, Count: Integer;
begin
  // 1900 tarih sistemi (Dates1904 = False), ay sonuna demirli seri
  MinSerial := TXLSChartDateAxisTransform.DateTimeToSerial(
    EncodeDate(2026, 1, 31), False);
  MaxSerial := TXLSChartDateAxisTransform.DateTimeToSerial(
    EncodeDate(2026, 12, 31), False);

  // Tek adım bir ay, 12 tick hedefle, 64'ü asla aşma
  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 içinde tek bir dürüst yaklaşım var. İstenen adım sıfır olduğunda yöntem, bir ayı 30 gün ve bir yılı 365 sayarak bir adım büyüklüğü tahmin eder, sonra ham adımı bir on kuvvetinin 1, 2 ya da 5 katına yuvarlar. Bu tahmin yalnızca tick'ler arasında kaç birim olduğuna karar verir. Tick konumlarının kendileri yine IncMonth ve IncYear'dan gelir; yaklaşım tick yoğunluğunu değiştirebilir ama hiçbir tick'i takvimden öteye taşıyamaz

TDateTime'a çevirmek yerine neden çalışma kitabı serial değerleri tutuluyor?

HotXLS'de bir tarih ekseni koordinat uzayı olarak çalışma kitabı serial değerlerini tutar, çünkü Delphi'nin TDateTime'ında serial 60 için bir yuva yoktur — 1900 tarih sisteminin uyumluluk tuhaflığı olarak devraldığı hayalet 29 Şubat 1900. TrySerialToDateTime 60 altındaki serial değerlerini bir gün kaydırır ve serial 60'ı 28 Şubat'a katlar; tek bir noktayı etiketlemek için sorun değil ama geometri için ölümcüldür: her noktayı önce TDateTime'a çevirirseniz hayalet günün iki yanı, Excel'in çizdiğinden bir birim daha yakın düşer ve Mart 1900 öncesinde çizilen her şey sonrasına göre kayar. TXLSChartDateAxisTransform.FormatValue aynı yuvayı özel işler ve tam olarak Excel gibi 1900-02-29 diye etiketler. 1904 çalışma kitabında serial 0, 1 Ocak 1904'tür ve transform sabit 1462 günlük kaydırmayı iki yönde de uygular; her iki dönemin tam hikâyesi Excel tarih serial değerleri, 1904 sistemi ve sayı formatları makalemizde

HotXLS tarih ekseninin TDateTime'a çevirmek yerine çalışma kitabı serial değerlerini tutması neden önemli: serial 60 hayalet 29 Şubat 1900'dur, TrySerialToDateTime 60 altını kaydırıp 60'ı 28 Şubat'a katladığında iki yan bir birim daha yakın düşer, FormatValue ise serial 60'ı 1900-02-29 diye etiketler
Katlama tek bir noktayı etiketlemek için sorun değil ama geometri için ölümcüldür, çünkü Mart 1900 öncesinde çizilen her şey sonrasına göre kayar; 1904 çalışma kitabı bunun yerine sabit 1462 günlük kaydırmayı uygular

HotXLS gün, ay ya da yılı otomatik nasıl seçiyor?

TXLSChartDateAxisTransform.DetectUnit, verideki boşlukların gerçekte ihtiyaç duyduğu en ince takvim birimini seçer. Yöntem önce NaN ve sonsuz değerleri atar, kalanları O(n log n) içinde bir kez sıralar ve yinelenen tarihleri atlar. Herhangi bir ardışık boşluk bir takvim ayından kısaysa birim gündür; değilse, herhangi bir boşluk bir takvim yılından kısaysa birim aydır; değilse yıldır. Test IncMonth(Previous, 1) > Current'tır, 30 günlük bir eşik değil; böylece 31 Ocak, 28 Şubat, 31 Mart diye akan bir ay sonu serisi doğru biçimde aylık veri olarak saptanır. İki ayrı geçerli tarihten az kaldığında ölçülecek boşluk yoktur ve yöntem güne döner

ResolveUnits sonra saptanan birimi grafiğin bildirdiğiyle birleştirir. Açık bir taban birime riayet edilir ve eksik bir major ya da minor birim, tabandan daha kaba olduğunda saptanan birimi devralır. Son adım herkesin gözünden kaçan adımdır: etkin major ya da minor birim taban birimden inceyse taban, eşleşmek için düşürülür. Taban birim, hiç tick var olmadan önce veri noktalarının normalize edildiği şeydir; Normalize bir değeri gününe, ayının birine ya da 1 Ocak'a yapıştırır, böylece günlük major birim altındaki aylık bir taban, tick kodu onları hiç görmeden bir aylık noktaları tek yuvaya çökertirdi. Tick yoğunluğu da sınırlıdır: çizim uzunluğu, eksen fontunun ölçülen genişliği, etiket sayı formatı ve döndürülmüş etiketlerin izdüşümüyle; her arka uç etiketleri, tick işaretlerini ve kılavuz çizgilerini tek bir paylaşımlı, sınırlı tick dizisinden çizer ve major tick ile çakışan minor tick yalnızca bir kez çizilir

HotXLS tarih ekseni birimlerini otomatik nasıl seçiyor: DetectUnit NaN ve sonsuz değerleri atar, bir kez sıralar ve takvim boşluklarını 30 günlük eşik yerine IncMonth ile ölçer, böylece 31 Ocak, 28 Şubat, 31 Mart serisi aylık saptanır; ResolveUnits açık bir tabana riayet eder, major birim daha ince olduğunda ise tabanı düşürür
Normalize, hiç tick var olmadan önce her değeri gününe, ayının birine ya da 1 Ocak'a yapıştırır; çünkü günlük major birim altındaki aylık bir taban, bir aylık noktaları tek yuvaya çökertirdi

dateAx öğesi neyi saklar, yokluk ne anlama gelir?

ChartML'de bir tarih eksenindeki her takvim ayarı isteğe bağlıdır ve birini yok saymak, varsayılan değerini yazmakla aynı şey değildir. ECMA-376 Part 1, §21.2 (DrawingML Charts) içindeki CT_DateAx tipi, baseTimeUnit, majorTimeUnit, minorTimeUnit ve auto'nun her birinin bulunup bulunmamasına izin verir; yok sayılan bir baseTimeUnit, Excel'e kendisinin karar vermesini söyler ve bu, açık bir days'ten farklıdır. TXLSXChartAxis bu yüzden her değeri bir varlık bayrağının yanında saklar: BaseTimeUnit ile BaseTimeUnitSet, MajorTimeUnit ile MajorTimeUnitSet, MinorTimeUnit ile MinorTimeUnitSet ve AutoDateAxis ile AutoDateAxisSet. Değer atamak bayrağını set eder, bayrağı temizlemek öğeyi yokluğa döndürür ve XLSX yazıcısı bir öğeyi yalnızca bayrağı set edilmişse üretir. auto'nun kategori-mi-tarih-mi otomatik saptaması ifade ettiğini, otomatik tick birimlerini değil, unutmayın

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 yazar
    Axis.BaseTimeUnit := xlsxChartTimeDays;     // BaseTimeUnitSet'i de set eder
    Axis.MajorTimeUnit := xlsxChartTimeMonths;
    Axis.MajorUnit := 1;                        // > 0, majorUnit'ü set edilmiş işaretler
    Axis.NumberFormat := 'mmm yyyy';
    Axis.NumberFormatSourceLinked := False;
    Axis.TextStyle.Rotation := -45;             // derece; 1/60000 olarak saklanır
    // AutoDateAxis hiç dokunulmadı: c:auto öğesi yazılmaz

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

Etiketler ve yönelim aynı varlık-duyarlı kuralları izler. Eksen numFmt'i başlığın ardından, tick işaretlerinden önce gelir; sourceLinked özniteliği varsayılan olarak true'dur ve TXLSXChartAxis format kodunu, source-linked değerini ve NumberFormatSet'i ayrı tutar, böylece boş bir kod asla olmayan bir öğe sanılmaz. Metin döndürmesi 1/60000 derece hassasiyetle saklanır, pozitif değerler saat yönünü ifade eder; TextStyle.Rotation düz derece alır, SVG çıktısı açıyı olduğu gibi kullanır ve pozitif açıları saat yönü tersine işleyen sayfalanmış arka uç işareti tek bir paylaşımlı sınırda çevirir. Ters çevrilmiş bir maxMin ekseni major ve minor tick'leri, kılavuz çizgilerini, veri noktalarını, trend çizgilerini ve hata çubuklarını birlikte aynalar, asla yalnızca etiketleri değil. Başka bir yerde yazılmış bir çalışma kitabı açtığınızda bayraklar, yazanın gerçekte ne belirttiğini söyler

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;   // tekrar kaydedin, yok sayılan öğeler yok sayılmış kalır
  end;
end;

Takvim modeli nerede durur?

Takvim modeli, takvim birimlerini ifade edemeyen formatlarda durur ve HotXLS bunun başka olduğunu iddia etmez. ODF 1.2, gün, ay ya da yıl kavramı olmayan düz bir sayı olan chart:interval-major sunar; bu yüzden ODS olarak kaydedilen bir Excel aylık ekseni birimini taşıyamaz ve HotXLS birim taklidi yapmak için standart dışı bir öznitelik uydurmaz. Eski BIFF8 tarafında AxcExt kaydı dokuz sabit 16-bit kelime taşır ve otomatik bayrakları alanları kaldırmak yerine yalnızca maskeler; HotXLS maskelenmiş minimum, maksimum, aralık, birim ve kesişim değerlerini okuma ve geri yazmada korur, bilinmeyen birim kodlarını Classic modelde olduğu gibi tutar ve yalnızca geçerli tarih birimleri 0, 1 ve 2'yi (gün, ay, yıl) XLSX'e eşler

Tarih eksenleri, oluşturmadığı dosyaların da hayatta kalmasına razı olmak zorunda olan bir grafik modelinin katmanlarından biridir. Bir Excel çalışma kitabı tarih-eksenli bir çizgiyi ikincil eksende bir sütun serisiyle birleştirdiğinde korunan ChartML ve kombinasyon grafikleri makalemiz, hiçbir şey değişmediğinde HotXLS'in özgün grafik XML'ini bayt bayt yeniden oynatmasını, bir şey değiştiğinde ise tipli düzenlemeleri ona merge etmesini gösterir; grafikler, resimler ve çizimler genel bakışımız yukarıda kullanılan çapa ve seri API'lerini kapsar. Hepsi, Excel otomasyonu olmadan XLS ve XLSX grafiklerini okuyan, yazan ve render eden, Delphi ve C++Builder için HotXLS Delphi Component içinde gelir