Artikel Teknis

Bagan, Gambar, dan Objek Gambar HotXLS di Delphi

Apa pun yang mengambang di atas grid worksheet (sebuah chart, sebuah logo, sebuah stempel, sebuah kotak callout) adalah sebuah objek drawing, dan sebuah objek drawing didefinisikan oleh dua hal: apa objek itu, dan di mana ia di-anchor. Anchor inilah bagian yang sering disalahpahami orang. Sebuah chart tidak tinggal di dalam sebuah cell; ia duduk dalam sebuah persegi panjang yang dipatok pada rentang baris dan kolom, dan data yang diplotnya adalah sekumpulan referensi A1 terpisah yang sama sekali tidak diketahui oleh anchor tersebut. Pindahkan frame-nya dan plot-nya tetap di tempat. Sisipkan baris di bawahnya dan frame itu ikut turun bersamanya. Menjaga kedua sistem koordinat ini tetap terpisah dengan jelas adalah sebagian besar dari apa yang membuat kode drawing berperilaku benar

HotXLS adalah library Object Pascal native yang membaca dan menulis XLS dan XLSX tanpa otomasi Excel, dan ia membawa dua model drawing yang terpisah karena kedua format file itu menyimpan drawing dengan cara yang berbeda. Format .xls BIFF8 menyimpan chart pada sheet khusus miliknya sendiri dan shape mengambang dalam sebuah stream OfficeArt yang melekat pada worksheet. Format .xlsx OOXML bisa menanamkan sebuah chart di dalam grid, di-anchor ke sebuah persegi panjang cell, berdampingan dengan jenis gambar dan shape mengambang yang sama. Model objeknya mencerminkan pemisahan itu, dan kegagalan-kegagalan yang layak dibahas semuanya berasal dari menerapkan aturan satu format ke format yang lain

Container mana yang bisa menampung apa

Pemilihan container harus dilakukan sebelum kode chart apa pun, karena jenis objek yang tersedia berbeda di antara keduanya:

Diagram yang membandingkan wadah gambar di HotXLS dari Delphi: chart sheet dan bentuk OfficeArt di XLS warisan versus chart tersemat, gambar, dan kotak teks di XLSX
Kedua format file memaparkan API drawing yang berbeda, sehingga container harus dipilih sebelum kode chart mana pun ditulis
  • XLS (BIFF8): chart tinggal pada sheet chart khusus yang dibuat melalui AddChartSheet pada collection Sheets. Gambar, text box, persegi panjang, oval, dan garis adalah shape OfficeArt yang dikelola melalui collection Shapes milik worksheet. Tidak ada API untuk menanamkan sebuah chart di dalam grid worksheet biasa
  • XLSX (OOXML): chart bisa ditanamkan langsung dalam sebuah worksheet dengan TXLSXWorksheet.AddChart, di-anchor ke sebuah persegi panjang cell, atau ditempatkan pada sebuah sheet chart khusus dengan TXLSXWorkbook.AddChartSheet. Gambar masuk dengan AddImage atau AddImageFromFile, dan label mengambang dengan AddTextBox

Jadi sebuah requirement yang dirumuskan sebagai "sebuah sheet dashboard dengan chart di samping angka-angkanya" sebenarnya adalah requirement untuk .xlsx. Anda hanya bisa mendekatinya di .xls dengan mendorong chart itu ke sheet-nya sendiri, yang mengubah cara pengguna menavigasi file itu dan mengubah bagaimana kode Anda harus berperilaku. Sheet yang dikembalikan oleh AddChartSheet versi XLS adalah sebuah chart substream, bukan grid: menulis ke sana dengan Cells.Item menghasilkan sebuah drawing stream yang tidak konsisten, yang dihasilkan tanpa error namun kemudian dibuang oleh Excel saat dibuka. Chart itu sekadar lenyap, dan tidak ada apa pun dalam build log yang menjelaskan mengapa. Perlakukan sheet yang dikembalikan itu sebagai chart-only dan seluruh kelas laporan "chart hilang" pun lenyap

Menanamkan sebuah chart dalam worksheet XLSX

Jalur XLSX adalah jalur yang punya ruang untuk bermanuver, dan di sinilah kedua sistem koordinat dari pembukaan menjadi konkret. Persegi panjang anchor yang diteruskan ke AddChart dinyatakan dalam baris dan kolom worksheet dan menetapkan di mana frame chart itu duduk. Data seri dinyatakan sebagai referensi A1 absolut yang menyertakan nama sheet. Keduanya independen: Anda bisa memindahkan frame ke sisi jauh sheet itu dan ia tetap memplot cell yang sama

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;

    // Frame di-anchor pada baris 6..22, kolom 1..8
    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;

Argumen yang bisa menggigit adalah string range yang diserahkan ke AddSeries. Itu adalah sebuah literal, ditangkap pada saat pemanggilan, dan sama sekali tidak tahu bahwa Anda mungkin menambahkan dua puluh baris data lagi sesudahnya. Bangun string itu dari jumlah baris yang Anda hitung setelah data ditulis, jangan pernah sebelumnya. Chart scatter dan bubble membebani kedua argumen yang sama dengan makna yang berbeda: range categories sekarang menyediakan nilai X dan range values menyediakan Y, dan radius bubble berasal dari sebuah referensi ketiga yang diatur melalui BubbleSizeRange pada TXLSXChartSeries yang dikembalikan. Bacalah pemanggilan itu sebagai "X, Y, size" alih-alih "categories, values" begitu Anda meninggalkan keluarga column-and-bar

TXLSXChartType mencakup plot column, bar, line, pie, area, doughnut, scatter, bubble, dan radar, yang mencakup repertoar report sehari-hari. Untuk sebuah chart satu halaman penuh tanpa grid di sekelilingnya, Book.AddChartSheet mengembalikan sebuah sheet yang property IsChartSheet-nya bernilai true. Ini adalah padanan .xlsx dari chart sheet legacy dan membawa ekspektasi yang sama: jangan menulis konten cell ke sana

Gambar masuk sebagai byte, dan ukurannya dalam EMU

Ada dua overload untuk menyisipkan sebuah gambar, dan mencampuradukkan keduanya adalah bug gambar yang paling sering muncul dalam code review. AddImage(ARow, ACol, AData, AFormat) menginginkan byte gambar yang sudah ter-encode dalam AData: konten mentah dari sebuah PNG, JPEG, GIF, atau BMP. Berikan sebuah path file dan Anda telah menyimpan sebuah string empat puluh byte yang tidak bisa di-decode viewer mana pun, dan itulah persis laporan ikon-gambar-rusak yang tidak ingin Anda debug setelah deployment. Ketika sumbernya adalah sebuah file di disk, panggil AddImageFromFile sebagai gantinya dan biarkan library membaca byte-nya serta mengklasifikasikan formatnya untuk Anda

Lalu datanglah soal ukuran. DrawingML tidak mengukur dalam piksel; ia mengukur dalam English Metric Unit, di mana 914400 EMU membentuk satu inci dan, pada 96 DPI, 9525 EMU membentuk satu piksel. Objek TXLSXImage mengekspos WidthEMU dan HeightEMU, sehingga sebuah logo yang dimaksudkan untuk ter-render 180 kali 60 piksel membutuhkan 1714500 kali 571500 EMU. Letakkan konversi itu dalam sebuah konstanta bernama dan hitung berdasarkan itu. Angka ajaib seperti 1714500 yang tersebar di seluruh kode tidak terbaca dan diam-diam menjadi salah pada saat pertama seseorang mengubah target DPI. Baris dan kolom anchor, kebetulan, berbasis 1, mencocokkan sisa cell API alih-alih matematika EMU yang berbasis 0

Diagram dua sistem koordinat di balik TXLSXWorksheet.AddChart di HotXLS: bingkai chart berjangkar ke baris dan kolom worksheet sementara data series-nya memakai referensi A1 absolut
Frame dipatok ke baris dan kolom sementara plot membaca referensi A1 absolut, dan tak satu pun sistem koordinat tahu tentang yang lain

Sheet chart dan shape dalam file XLS legacy

Di sisi BIFF8, overload AddChartSheet yang lebih kaya menerima jenis chart, judul axis, dan sebuah open array record TXLSChartSeriesInfo, di mana setiap record menyimpan sebuah nama serta range categories dan values sebagai string. Shape mengambang adalah persoalan terpisah: mereka berada pada worksheet data itu sendiri, melalui collection Shapes-nya, bukan pada sheet chart

var
  Book: IXLSWorkbook;
  Data, Trend: IXLSWorksheet;
  Series: array[0..0] of TXLSChartSeriesInfo;
begin
  Book := TXLSWorkbook.Create;   // dihitung lewat interface: jangan Free
  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 adalah sebuah chart substream: jangan pernah panggil method cell padanya

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

Ada dua detail masa hidup objek yang penting di sini, dan keduanya menarik ke arah yang berlawanan. TXLSWorkbook dipegang melalui interface IXLSWorkbook dan dihitung secara reference-counted, sehingga memanggil Free padanya sendiri memicu pelepasan ganda. TXLSXWorkbook dari bagian-bagian sebelumnya adalah sebuah objek biasa dan harus dibebaskan dalam sebuah try..finally. Reviewer kode yang sama yang menandai Free yang hilang di sisi XLSX harus menandai yang ada di sisi XLS, yang menjadi jebakan nyata ketika Anda bekerja dengan kedua format dalam unit yang sama. Helper shape itu sendiri seragam: AddRectangle, AddOval, dan AddLine, dengan DeleteInRange untuk membersihkan sebuah region drawing, semuanya di-anchor oleh pasangan baris dan kolom, sehingga sebuah template yang menyisipkan baris di atasnya menggeser mereka bersama dengan grid

Satu property lagi yang terbukti berharga pada file legacy. TXLSPicture.TransparentColor menyembunyikan sebuah warna latar belakang terpilih dari sebuah bitmap, dan itulah cara Anda menjatuhkan sebuah stempel non-persegi panjang (sebuah segel "Approved", sebuah watermark) di atas grid dalam sebuah format yang rendering BIFF-nya tidak pernah mengenal alpha PNG. Atur warna yang menjadi latar saat stempel itu dibuat dan persegi panjang di sekelilingnya pun menghilang

Warna tema tidak selamat melewati round-trip BIFF8

Fill drawing OOXML bisa menunjuk ke sebuah slot warna tema, dan itulah sebabnya mewarnai ulang seluruh .xlsx dengan mengganti tema-nya menjadi murah. Record drawing BIFF8 tidak memiliki slot semacam itu. Ketika HotXLS menerapkan sebuah warna tema pada sebuah drawing XLS, ia menyelesaikan warna itu menjadi sebuah nilai RGB literal dan menyimpannya; indeks tema asalnya lenyap begitu file itu ditulis, dan membuka kembali tidak bisa memulihkannya. Ini terutama menjebak tool reporting white-label, jenis yang me-re-brand dokumen hasil generasi yang sama untuk banyak pelanggan. Simpan pemetaan theme-ke-RGB dalam konfigurasi Anda sendiri dan terapkan ulang setiap kali Anda melakukan generasi, alih-alih mengharapkan bisa membacanya kembali dari sebuah .xls yang tersimpan

Diagram penyisipan gambar HotXLS dari Delphi: AddImage menghendaki byte terenkode sementara AddImageFromFile membaca berkas, dan piksel 96 DPI dikonversi ke nilai WidthEMU dan HeightEMU
Byte gambar dan path file termasuk overload yang berbeda, dan ukuran piksel di layar dikonversi ke EMU sebelum mencap objek gambar

Sebuah keputusan terkait muncul di sisi performa. Facade XLS bisa diberi tahu untuk melewatkan sepenuhnya parsing lapisan drawing ketika yang Anda inginkan dari sebuah file legacy besar hanyalah data cell-nya, dengan mengatur _DisableGraphics ke true, dan itu memangkas waktu nyata dari bulk read. Jebakannya bersifat permanen: sebuah workbook yang dibuka dengan cara itu tidak memiliki stream OfficeArt di memori, sehingga menyimpannya menulis drawing itu keluar dari eksistensinya. Cadangkan flag ini untuk job analitik read-only. Gambaran performa yang lebih luas ada di catatan kami tentang performa workbook besar di HotXLS

Menjaga anchor tetap stabil saat grid berubah

Report jarang tetap berukuran sama seperti saat dihasilkan, dan di sinilah model anchor dari pembukaan artikel ini terbukti berharga. Operasi struktural facade XLSX (InsertRows, DeleteRows, dan padanan kolomnya) memindahkan lapisan-lapisan yang bergantung bersama dengan cell-nya. Region gabungan, hyperlink, komentar, panel beku, range filter, format kondisional, validasi, tabel, nama yang didefinisikan, dan, untuk topik ini, anchor gambar dan chart semuanya berpindah bersama. Sebuah logo yang di-anchor pada baris 1 tetap berada di atas ketika sepuluh baris masuk di bawahnya. Sebuah frame chart yang di-anchor di bawah blok data ikut turun seiring blok itu bertumbuh. Satu hal yang tidak ditulis ulang adalah string range mana pun yang Anda tangkap sebagai literal sebelum penyisipan terjadi, karena itu hanya teks yang tidak ada alasan bagi library untuk meninjaunya kembali. Itulah yang menentukan urutan aman untuk pengisian template: tulis dan susun ulang data terlebih dahulu, lalu buat chart dan tempatkan gambar sebagai langkah terakhir, dengan setiap string range diturunkan dari jumlah baris yang Anda miliki setelah penyisipan, bukan sebelumnya

Dua tool yang lebih kecil melengkapi kit penempatan ini. TXLSTextBox.SetArea di sisi XLS meng-anchor ulang sebuah text box atau auto shape yang sudah ada ke sebuah persegi panjang cell baru, yang lebih baik daripada menghapus dan membuatnya ulang ketika sebuah blok footer bergeser. Dan overload bitmap milik AddPicture menerima sebuah TBitmap hidup dengan flag transparansi opsional, sehingga apa pun yang bisa digambar kode VCL Anda sendiri (sebuah gauge, sebuah strip sparkline, sebuah jenis chart yang tidak ditawarkan daftar native) bisa distempel langsung ke dalam sheet tanpa perlu menulis file sementara terlebih dahulu

Chart dan gambar hampir selalu menjadi lapisan penyelesaian pada sebuah report yang strukturnya sudah tertata, dan itulah sebabnya fondasinya menentukan apakah keduanya mendarat dengan mulus. Mengisi data yang akan direferensikan sebuah chart dibahas dalam template-driven report generation, dan menjaga grid tetap stabil di bawah anchor Anda adalah topik dari merged cells dan kontrol layout. Dokumentasi class dan method lengkap ada di halaman produk HotXLS Delphi Component