Teknik Makale

HotXLS Grafik ve Görüntüler: Delphi ile Excel Çizimleri

Bir çalışma sayfası kılavuzunun üzerinde yüzen her şey (bir grafik, bir logo, bir damga, bir açıklama kutusu) bir çizim nesnesidir ve bir çizim nesnesi iki şeyle tanımlanır: ne olduğu ve nereye çapalandığı. İnsanların yanlış yaptığı kısım çapadır. Bir grafik bir hücrede yaşamaz; satır ve sütun aralığına iğnelenmiş bir dikdörtgende oturur ve çizdiği veri, çapanın hiç haberdar olmadığı ayrı bir A1 başvuru kümesidir. Çerçeveyi taşırsınız, çizim yerinde kalır. Altına satır eklersiniz, çerçeve onlarla birlikte aşağı kayar. Bu iki koordinat sistemini birbirine karıştırmamak, çizim kodunun düzgün davranmasının neredeyse tamamıdır

HotXLS, XLS ve XLSX dosyalarını Excel otomasyonu olmadan okuyup yazan yerel bir Object Pascal kütüphanesidir ve iki dosya biçimi çizimleri farklı sakladığı için iki ayrı çizim modeli taşır. BIFF8 .xls biçimi grafikleri kendilerine ayrılmış sayfalarda, yüzen şekilleri ise çalışma sayfasına iliştirilmiş bir OfficeArt akışında tutar. OOXML .xlsx biçimi ise bir grafiği kılavuzun içine, bir hücre dikdörtgenine çapalanmış olarak gömebilir; aynı türden yüzen resimler ve şekiller de onun yanında durur. Nesne modeli bu ayrımı yansıtır ve yazmaya değer başarısızlıkların tamamı, bir biçimin kurallarını diğerine uygulamaktan doğar

Hangi kap neyi tutabilir

Kap tercihi her grafik kodundan önce gelmelidir, çünkü kullanılabilir nesne türleri ikisi arasında farklıdır:

  • XLS (BIFF8): grafikler, Sheets koleksiyonu üzerindeki AddChartSheet ile oluşturulan özel grafik sayfalarında yaşar. Resimler, metin kutuları, dikdörtgenler, ovaller ve çizgiler, çalışma sayfasının Shapes koleksiyonuyla yönetilen OfficeArt şekilleridir. Normal bir çalışma sayfası kılavuzunun içine grafik gömmek için API yoktur
  • XLSX (OOXML): grafikler TXLSXWorksheet.AddChart ile doğrudan bir çalışma sayfasına, bir hücre dikdörtgenine çapalanarak gömülebilir ya da TXLSXWorkbook.AddChartSheet ile özel bir grafik sayfasına yerleştirilebilir. Görüntüler AddImage ya da AddImageFromFile ile, yüzen etiketler ise AddTextBox ile girer
Delphi tarafından HotXLS içindeki çizim kaplarını karşılaştıran şema: eski XLS biçimindeki grafik sayfaları ve OfficeArt şekilleri ile XLSX biçimindeki gömülü grafikler, görüntüler ve metin kutuları
İki dosya biçimi farklı çizim API bileşenleri sunar, dolayısıyla kap, herhangi bir grafik kodu yazılmadan önce seçilmelidir

Dolayısıyla "sayıların yanında grafiği olan bir gösterge sayfası" diye ifade edilen bir gereksinim, aslında .xlsx gereksinimidir. Bunu .xls içinde ancak grafiği kendi sayfasına iterek yaklaşık olarak karşılayabilirsiniz; bu da kullanıcının dosyada nasıl gezindiğini ve kodunuzun nasıl davranması gerektiğini değiştirir. XLS tarafındaki AddChartSheet çağrısının döndürdüğü sayfa bir kılavuz değil, bir grafik alt akışıdır: ona Cells.Item ile yazmak, hatasız üretilen ama Excel tarafından açılışta atılan tutarsız bir çizim akışı doğurur. Grafik öylece yok olur ve derleme günlüğünde nedenini söyleyen hiçbir şey yoktur. Dönen sayfayı yalnızca grafik olarak ele alın; "grafik kayıp" bildirimlerinin tüm sınıfı ortadan kalksın

Bir XLSX çalışma sayfasına grafik gömme

XLSX yolu manevra alanı olan yoldur ve girişteki iki koordinat sisteminin somutlaştığı yer burasıdır. AddChart çağrısına verilen çapa dikdörtgeni çalışma sayfası satır ve sütunlarıyla ifade edilir ve grafik çerçevesinin nerede oturacağını sabitler. Veri serisi ise sayfa adını içeren mutlak A1 başvurularıyla ifade edilir. Bunlar birbirinden bağımsızdır: çerçeveyi sayfanın öbür ucuna taşıyabilirsiniz, yine aynı hücreleri çizer

HotXLS içindeki TXLSXWorksheet.AddChart çağrısının ardındaki iki koordinat sisteminin şeması: çalışma sayfası satır ve sütunlarına çapalanan grafik çerçevesi ile mutlak A1 başvuruları kullanan seri verisi
Çerçeve satır ve sütunlara iğnelenir, çizim ise mutlak A1 başvurularını okur ve iki koordinat sisteminin de diğerinden haberi yoktur
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Chart: TXLSXChart;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Sales');
    Sheet.Cells[1, 1].Value := 'Region';
    Sheet.Cells[1, 2].Value := 'Revenue';
    Sheet.Cells[2, 1].Value := 'East';
    Sheet.Cells[2, 2].Value := 1184350;
    Sheet.Cells[3, 1].Value := 'Central';
    Sheet.Cells[3, 2].Value := 902210;
    Sheet.Cells[4, 1].Value := 'West';
    Sheet.Cells[4, 2].Value := 1010675;

    // Çerçeve 6..22 satırlarına, 1..8 sütunlarına çapalanır
    Chart := Sheet.AddChart(xlsxChartColumn, 'Revenue by Region', 6, 1, 22, 8);
    Chart.AddSeries('Revenue', 'Sales!$A$2:$A$4', 'Sales!$B$2:$B$4');
    Chart.ValueAxisTitle := 'USD';

    Sheet.AddImageFromFile(1, 5, 'logo.png');
    Book.SaveAs('dashboard.xlsx');
  finally
    Book.Free;
  end;
end;

Isıran argüman, AddSeries çağrısına verilen aralık dizesidir. Bu bir değişmezdir, çağrı anında yakalanır ve sonradan yirmi satır daha veri ekleyebileceğinizden haberi yoktur. Onu, veri yazıldıktan sonra hesapladığınız bir satır sayısından kurun, asla öncesinden değil. Dağılım ve kabarcık grafikleri aynı iki argümana farklı anlamlar yükler: kategori aralığı artık X değerlerini, değer aralığı ise Y değerlerini sağlar ve kabarcık yarıçapı, dönen TXLSXChartSeries üzerindeki BubbleSizeRange ile ayarlanan üçüncü bir başvurudan gelir. Sütun ve çubuk ailesinden çıktığınızda çağrıyı "kategoriler, değerler" değil "X, Y, boyut" diye okuyun

TXLSXChartType; sütun, çubuk, çizgi, pasta, alan, halka, dağılım, kabarcık ve radar çizimlerini kapsar ki bu da gündelik raporlama dağarcığını karşılar. Çevresinde kılavuz olmayan tam sayfa bir grafik için Book.AddChartSheet, IsChartSheet özelliği true olan bir sayfa döndürür. Bu, eski grafik sayfasının .xlsx karşılığıdır ve aynı beklentiyi taşır: ona hücre içeriği yazmayın

Görüntüler bayt olarak girer ve EMU cinsinden boyutlanır

Resim eklemek için iki aşırı yükleme vardır ve bunları karıştırmak, kod incelemesinde en sık görünen görüntü hatasıdır. AddImage(ARow, ACol, AData, AFormat), AData içinde zaten kodlanmış resim baytlarını ister: bir PNG, JPEG, GIF ya da BMP dosyasının ham içeriğini. Ona bir dosya yolu verirseniz, hiçbir görüntüleyicinin çözemeyeceği kırk baytlık bir dize saklamış olursunuz ki bu da dağıtımdan sonra hata ayıklamak istemeyeceğiniz kırık görüntü simgesi bildiriminin ta kendisidir. Kaynak diskteki bir dosyaysa bunun yerine AddImageFromFile çağırın ve baytları okumayı, biçimi sınıflandırmayı kütüphaneye bırakın

Sonra boyutlandırma gelir. DrawingML piksel cinsinden ölçmez; bir inçin 914400 EMU ettiği ve 96 DPI değerinde bir pikselin 9525 EMU ettiği İngiliz Metrik Birimleri cinsinden ölçer. TXLSXImage nesnesi WidthEMU ve HeightEMU alanlarını açığa çıkarır, dolayısıyla 180 x 60 piksel işlenmesi gereken bir logo 1714500 x 571500 EMU ister. Bu dönüşümü adlandırılmış bir sabite koyun ve hesabı ona göre yapın. Kodun içine serpiştirilmiş 1714500 gibi sihirli sayılar okunmazdır ve biri hedef DPI değerini ilk değiştirdiğinde sessizce yanlışa döner. Bu arada çapa satırı ve sütunu 1 tabanlıdır; 0 tabanlı EMU aritmetiğine değil, hücre API bileşeninin geri kalanına uyar

Delphi tarafından HotXLS görüntü ekleme şeması: AddImage kodlanmış baytları isterken AddImageFromFile dosyayı okur ve 96 DPI piksel ölçüleri WidthEMU ile HeightEMU değerlerine dönüşür
Resim baytları ile dosya yolları farklı aşırı yüklemelere aittir ve ekrandaki piksel ölçüleri görüntü nesnesine ulaşmadan önce EMU değerlerine dönüşür

Eski XLS dosyalarında grafik sayfaları ve şekiller

BIFF8 tarafında zengin AddChartSheet aşırı yüklemesi grafik türünü, eksen başlıklarını ve her kaydın bir ad ile dize olarak bir kategori ve değer aralığı tuttuğu açık bir TXLSChartSeriesInfo kayıt dizisini alır. Yüzen şekiller ayrı bir konudur: grafik sayfasına değil, veri çalışma sayfasının kendisine, onun Shapes koleksiyonu üzerinden girer

var
  Book: IXLSWorkbook;
  Data, Trend: IXLSWorksheet;
  Series: array[0..0] of TXLSChartSeriesInfo;
begin
  Book := TXLSWorkbook.Create;   // arayüz sayımlı: Free çağırmayın
  Data := Book.Sheets.Add;
  Data.Name := 'Data';
  Data.Cells.Item[1, 1].Value := 'Month';
  Data.Cells.Item[1, 2].Value := 'Units';
  Data.Cells.Item[2, 1].Value := 'Apr';
  Data.Cells.Item[2, 2].Value := 1530;
  Data.Cells.Item[3, 1].Value := 'May';
  Data.Cells.Item[3, 2].Value := 1721;

  Series[0].Name := 'Units';
  Series[0].Categories := 'Data!$A$2:$A$3';
  Series[0].Values := 'Data!$B$2:$B$3';
  Trend := Book.Sheets.AddChartSheet('Trend', xlsChartTypeLine,
    'Units sold', 'Month', 'Units', Series);
  // Trend bir grafik alt akışıdır: üzerinde asla hücre yöntemi çağırmayın

  Data.Shapes.AddTextBox('Source: ERP nightly export', 6, 1, 8, 4);
  Data.Shapes.AddPicture('approved-stamp.bmp');
  Book.SaveAs('trend.xls');
end;

Burada iki yaşam süresi ayrıntısı önemlidir ve ikisi ters yönlere çeker. TXLSWorkbook, IXLSWorkbook arayüzü üzerinden tutulur ve başvuru sayımlıdır, dolayısıyla onun üzerinde kendiniz Free çağırmanız çift serbest bırakmayı tetikler. Önceki bölümlerdeki TXLSXWorkbook ise düz bir nesnedir ve bir try..finally içinde serbest bırakılmalıdır. XLSX tarafında eksik bir Free çağrısını işaretleyen kod gözden geçiren, XLS tarafında var olan bir tanesini işaretlemek zorundadır; aynı birim içinde iki biçimle birden çalışırken bu gerçek bir tökezleme noktasıdır. Şekil yardımcılarının kendileri ise tektiptir: AddRectangle, AddOval ve AddLine, çizimlerden bir bölgeyi temizlemek için de DeleteInRange; hepsi satır ve sütun çiftleriyle çapalanır, dolayısıyla üzerlerine satır ekleyen bir şablon onları kılavuzla birlikte kaydırır

Eski dosyalarda bir özellik daha ekmeğini çıkarır. TXLSPicture.TransparentColor, seçtiğiniz bir arka plan rengini bir bit eşlemden maskeler; BIFF işlemesi PNG saydamlığını hiç öğrenmemiş bir biçimde dikdörtgen olmayan bir damgayı (bir "Approved" mührü, bir filigran) kılavuzun üzerine böyle bırakırsınız. Damganın hangi renge göre hazırlandığını ayarlayın, çevresindeki dikdörtgen kaybolsun

Tema renkleri BIFF8 gidiş dönüşünden sağ çıkmaz

OOXML çizim dolguları bir tema rengi yuvasını gösterebilir; bir .xlsx dosyasının tamamını temasını değiştirerek yeniden renklendirmenin ucuz olmasının nedeni budur. BIFF8 çizim kayıtlarında böyle bir yuva yoktur. HotXLS bir XLS çizimine tema rengi uyguladığında rengi değişmez bir RGB değerine çözer ve onu saklar; geldiği tema dizini, dosya yazıldığı anda kaybolur ve yeniden açmak onu geri getiremez. Bu özellikle beyaz etiketli raporlama araçlarını, yani aynı üretilmiş belgeyi birçok müşteri için yeniden markalayan türü yakalar. Tema ile RGB eşlemesini kendi yapılandırmanızda tutun ve kaydedilmiş bir .xls dosyasından geri okumayı beklemek yerine her üretimde yeniden uygulayın

Başarımla ilgili bağlantılı bir karar da ortaya çıkar. XLS cephesine, büyük bir eski dosyadan istediğiniz tek şey hücre verisiyse çizim katmanını tümüyle ayrıştırmayı atlaması söylenebilir; bunun için _DisableGraphics değerini true yapmak yeterlidir ve bu, toplu okumalardan gerçek süre kısar. Yakalayan kısım kalıcıdır: böyle açılmış bir çalışma kitabının bellekte OfficeArt akışı yoktur, dolayısıyla onu kaydetmek çizimleri yok eder. Bayrağı salt okunur analitik işlere saklayın. Geniş başarım tablosu HotXLS içinde büyük çalışma kitabı başarımı üzerine notlarımızda yer alır

Kılavuz değişirken çapaları kararlı tutmak

Raporlar nadiren üretildikleri boyutta kalır ve girişteki çapa modeli kendini burada amorti eder. XLSX cephesinin yapısal işlemleri (InsertRows, DeleteRows ve sütun karşılıkları) bağımlı katmanları hücrelerle birlikte taşır. Birleştirilmiş bölgeler, köprüler, açıklamalar, dondurulmuş bölmeler, süzgeç aralıkları, koşullu biçimler, doğrulamalar, tablolar, tanımlanmış adlar ve bu konu açısından görüntü ile grafik çapaları hep birlikte yolculuk eder. 1. satıra çapalanmış bir logo, altına on satır girdiğinde üstte kalır. Veri bloğunun altına çapalanmış bir grafik çerçevesi, blok büyüdükçe aşağı kayar. Yeniden yazılmayan tek şey, ekleme gerçekleşmeden önce değişmez olarak yakaladığınız her aralık dizesidir, çünkü o yalnızca metindir ve kütüphanenin ona dönmek için bir nedeni yoktur. Bu, bir şablon doldurma için güvenli sırayı belirler: önce veriyi yazın ve biçimlendirin, grafikleri oluşturup görüntüleri yerleştirmeyi ise son geçiş olarak yapın; her aralık dizesi de eklemelerden önceki değil, sonraki satır sayılarından türetilsin

İki küçük araç yerleştirme takımını tamamlar. XLS tarafındaki TXLSTextBox.SetArea, var olan bir metin kutusunu ya da otomatik şekli yeni bir hücre dikdörtgenine yeniden çapalar ki bir altbilgi bloğu kaydığında bu, onu silip yeniden oluşturmaktan iyidir. AddPicture çağrısının bit eşlem aşırı yüklemesi ise isteğe bağlı bir saydamlık bayrağıyla canlı bir TBitmap alır, dolayısıyla kendi VCL kodunuzun çizebildiği her şey (bir gösterge, bir mini grafik şeridi, yerleşik listede bulunmayan bir grafik türü) önce geçici bir dosya yazmadan doğrudan sayfaya damgalanabilir

Grafikler ve görüntüler neredeyse her zaman halihazırda yapılandırılmış bir raporun son katmanıdır; temiz oturup oturmayacaklarına da bu yüzden altyapı karar verir. Bir grafiğin başvuracağı veriyi doldurma konusu şablon güdümlü rapor üretimi yazısında, çapalarınızın altındaki kılavuzu kararlı tutmak ise birleştirilmiş hücreler ve düzen denetimi yazısında ele alınmaktadır. Eksiksiz sınıf ve yöntem belgeleri HotXLS Delphi Bileşeni ürün sayfasında bulunur