Technical Article

HotXLS grafikoni, slike i objekti za crtanje u Delphi-ju

Sve što lebdi iznad mreže radnog lista (grafikon, logo, pečat, okvir sa tekstom) jeste objekat za crtanje, a objekat za crtanje je definisan dvema stvarima: time šta je i gde je usidren. Sidro (anchor) je deo u kom ljudi najčešće greše. Grafikon ne živi u ćeliji; on se nalazi u pravougaoniku prikačenom za opseg redova i kolona, a podaci koje prikazuje su zaseban skup A1 referenci o kojima sidro ne zna ništa. Pomerite okvir i prikaz grafikona ostaje na mestu. Umetnite redove ispod njega i okvir klizi nadole zajedno sa njima. Održavanje ova dva koordinatna sistema jasnim predstavlja najveći deo uspešnog pisanja koda za crtanje.

HotXLS je izvorna Object Pascal biblioteka koja čita i upisuje XLS i XLSX formate bez automatizacije Excel-a, i sadrži dva odvojena modela crtanja jer ova dva formata fajlova čuvaju crteže na različite načine. BIFF8 .xls format čuva grafikone na sopstvenim namenskim listovima, a plutajuće oblike u OfficeArt toku koji je pridružen radnom listu. OOXML .xlsx format može da ugradi grafikon direktno unutar mreže ćelija, usidren za pravougaonik ćelije, zajedno sa istom vrstom plutajućih slika i oblika. Objektni model odražava ovu podelu, a neuspesi o kojima vredi pisati dolaze isključivo od primene pravila jednog formata na drugi.

Koji kontejner može šta da sadrži

Izbor kontejnera mora se izvršiti pre bilo kog koda za grafikone, jer se dostupni tipovi objekata razlikuju između ova dva formata:

  • XLS (BIFF8): grafikoni žive na namenskim listovima grafikona kreiranim preko funkcije AddChartSheet u kolekciji Sheets. Slike, tekstualni okviri, pravougaonici, ovali i linije su OfficeArt oblici kojima se upravlja preko kolekcije Shapes radnog lista. Ne postoji API za ugradnju grafikona unutar normalne mreže radnog lista.
  • XLSX (OOXML): grafikoni se mogu ugraditi direktno u radni list pomoću TXLSXWorksheet.AddChart, usidreni za pravougaonik ćelije, ili postaviti na namenski list grafikona pomoću TXLSXWorkbook.AddChartSheet. Slike se unose pomoću AddImage ili AddImageFromFile, a plutajuće oznake pomoću AddTextBox.

Dakle, zahtev formulisan kao "kontrolna tabla sa grafikonom pored brojeva" zapravo predstavlja zahtev za .xlsx format. U .xls formatu to možete samo približno rešiti prebacivanjem grafikona na sopstveni list, što menja način na koji korisnik navigira kroz fajl i kako vaš kod mora da se ponaša. List koji vraća funkcija AddChartSheet sa XLS strane je podtok grafikona, a ne mreža: upisivanje u njega pomoću Cells.Item proizvodi nekonzistentan tok crtanja koji se generiše bez greške, ali ga Excel potom odbacuje prilikom otvaranja. Grafikon jednostavno nestaje, a u build logu nema objašnjenja zašto. Tretirajte vraćeni list isključivo kao list grafikona i cela ta klasa prijava o "nestalom grafikonu" će nestati.

Ugradnja grafikona u XLSX radni list

XLSX putanja pruža više prostora za rad, i to je mesto gde dva koordinatna sistema sa početka postaju konkretna. Pravougaonik sidra prosleđen funkciji AddChart izražen je u redovima i kolonama radnog lista i fiksira mesto gde stoji okvir grafikona. Podaci serije su izraženi kao apsolutne A1 reference koje uključuju naziv lista. Oni su nezavisni: možete pomeriti okvir na suprotnu stranu lista, a on će i dalje iscrtavati iste ćelije.

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 anchored to rows 6..22, columns 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;

Argument koji pravi probleme je string opsega koji se prosleđuje funkciji AddSeries. To je literal, zabeležen u trenutku poziva, i on nema predstavu o tome da biste kasnije mogli dodati još dvadeset redova podataka. Izgradite ga na osnovu broja redova koji ste izračunali nakon što su podaci upisani, nikada pre toga. Grafikoni disperzije (scatter) i mehurića (bubble) preopterećuju ista dva argumenta sa različitim značenjima: opseg kategorija sada daje X vrednosti, opseg vrednosti daje Y, dok poluprečnik mehurića dolazi iz treće reference postavljene preko BubbleSizeRange na vraćenom TXLSXChartSeries objektu. Čitajte poziv kao "X, Y, veličina" umesto "kategorije, vrednosti" čim izađete iz familije stubaca i traka.

Tipovi TXLSXChartType pokrivaju stubaste, trakaste, linijske, kružne, površinske, prstenaste, disperzione, mehuričaste i radarske grafikone, što obuhvata svakodnevni repertoar izveštavanja. Za grafikon preko cele stranice bez okolne mreže ćelija, Book.AddChartSheet vraća list čije je svojstvo IsChartSheet tačno. To je .xlsx ekvivalent nasleđenog lista grafikona i nosi isto pravilo: nemojte upisivati sadržaj ćelija u njega.

Slike se unose kao bajtovi i mere se u EMU jedinicama

Postoje dva preopterećenja za umetanje slike, a njihovo mešanje je najčešći bag sa slikama u pregledima koda. AddImage(ARow, ACol, AData, AFormat) zahteva već kodirane bajtove slike u parametru AData: sirovi sadržaj PNG, JPEG, GIF ili BMP fajla. Prosledite mu putanju do fajla i time ćete upisati string od četrdeset bajtova koji nijedan pregledač ne može da dekodira, što dovodi do neispravnog prikaza slike koji sigurno ne želite da otklanjate nakon implementacije. Kada je izvor fajl na disku, pozovite AddImageFromFile i pustite biblioteku da sama pročita bajtove i prepozna format.

Zatim dolazi dimenzionisanje. DrawingML ne meri u pikselima; on koristi English Metric Units (EMU), gde 914400 EMU čini jedan inč, a pri rezoluciji od 96 DPI, jedan piksel iznosi 9525 EMU. Objekat TXLSXImage izlaže WidthEMU i HeightEMU, pa je za logo koji treba prikazati u veličini 180 sa 60 piksela potrebno 1714500 sa 571500 EMU. Postavite tu konverziju u imenovanu konstantu i računajte na osnovu nje. "Magični brojevi" poput 1714500 razbacani po kodu su nečitljivi i postaće pogrešni čim neko promeni ciljani DPI. Red i kolona sidra su zasnovani na indeksu 1, što odgovara ostatku API-ja za ćelije, za razliku od EMU matematike zasnovane na nuli.

Listovi grafikona i oblici u nasleđenim XLS fajlovima

Na strani BIFF8 formata, bogatije preopterećenje AddChartSheet prihvata tip grafikona, nazive osa i otvoreni niz zapisa TXLSChartSeriesInfo, gde svaki zapis sadrži naziv i opsege kategorija i vrednosti kao stringove. Plutajući oblici su zasebna stvar: oni idu na sam radni list sa podacima, kroz njegovu kolekciju Shapes, a ne na list grafikona.

var
  Book: IXLSWorkbook;
  Data, Trend: IXLSWorksheet;
  Series: array[0..0] of TXLSChartSeriesInfo;
begin
  Book := TXLSWorkbook.Create;   // interface-counted: do not 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 is a chart substream: never call cell methods on it

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

Ovde su važna dva detalja o životnom veku objekata, koji deluju u suprotnim smerovima. Klasa TXLSWorkbook se koristi kroz interfejs IXLSWorkbook i ima brojanje referenci, pa ručno pozivanje Free nad njom izaziva dvostruko oslobađanje. Klasa TXLSXWorkbook iz prethodnih odeljaka je običan objekat i mora se osloboditi unutar try..finally bloka. Isti revizor koda koji označi nedostatak poziva Free na XLSX strani mora da označi njegovo prisustvo na XLS side, što je prava zamka kada radite sa oba formata u istoj jedinici koda. Pomoćne funkcije za oblike su uniformne: AddRectangle, AddOval i AddLine, uz DeleteInRange za uklanjanje crteža iz određene regije, a sve se sidre preko parova redova i kolona, pa šablon koji umeće redove iznad njih pomera i njih zajedno sa mrežom.

Još jedno svojstvo je veoma korisno kod nasleđenih fajlova. TXLSPicture.TransparentColor maskira izabranu pozadinsku boju iz bitmape, što predstavlja način na koji možete postaviti nepravilni pečat (npr. pečat "Approved", vodeni žig) preko mreže u formatu čije BIFF renderovanje nikada nije podržavalo PNG alfa kanal. Postavite boju sa kojom je pečat kreiran i okolni pravougaonik nestaje.

Boje tema ne preživljavaju BIFF8 konverziju

OOXML ispune crteža mogu da ukazuju na slot boje teme (theme color slot), zbog čega je promena boja celog .xlsx fajla zamenom teme veoma jednostavna. BIFF8 zapisi crteža nemaju takav slot. Kada HotXLS primeni boju teme na XLS crtež, on razrešava tu boju u konkretnu RGB vrednost i nju čuva; indeks teme iz koje je potekla nestaje onog trenutka kada se fajl zapiše, i ponovno otvaranje ga ne može povratiti. Ovo se posebno odnosi na alate za izveštavanje bez oznake brenda (white-label), koji menjaju brend istog generisanog dokumenta za različite klijente. Čuvajte mapiranje tema u RGB u sopstvenoj konfiguraciji i primenite ga svaki put kada generišete fajl, umesto da očekujete da ga pročitate nazad iz sačuvanog .xls dokumenta.

Slična odluka se javlja i na strani performansi. XLS interfejsu se može reći da potpuno preskoči parsiranje sloja za crtanje kada iz velikog nasleđenog fajla želite samo podatke iz ćelija, postavljanjem _DisableGraphics na tačno, što štedi vreme pri masovnom čitanju. Posledica je trajna: radna sveska otvorena na taj način nema OfficeArt tok u memoriji, pa njeno čuvanje trajno uklanja crteže. Rezervišite ovu zastavicu za read-only poslove analitike. Šira slika o performansama nalazi se u našim beleškama o performansama velikih radnih svezaka u HotXLS-u.

Održavanje sidra stabilnim dok se mreža menja

Izveštaji retko ostaju iste veličine kao kada su generisani, i tu model sidra sa početka pokazuje svoju vrednost. Strukturne operacije XLSX interfejsa (InsertRows, DeleteRows i ekvivalenti za kolone) pomeraju zavisne slojeve zajedno sa ćelijama. Spojene ćelije, hiperlinkovi, komentari, zamrznuta polja, opsezi filtera, uslovni formati, validacije, tabele, definisana imena, kao i sidra slika i grafikona – sve se to pomera zajedno. Logo usidren u 1. redu ostaje na vrhu kada se ispod njega umetne deset redova. Okvir grafikona usidren ispod bloka podataka klizi nadole kako blok raste. Jedina stvar koja se ne prepisuje jeste string opsega koji ste zabeležili kao literal pre nego što se umetanje desilo, jer je to običan tekst koji biblioteka nema razloga da menja. To definiše siguran redosled popunjavanja šablona: najpre upišite i oblikujte podatke, a kreiranje grafikona i postavljanje slika ostavite za finalni prolaz, pri čemu svaki string opsega treba da bude izveden iz broja redova koji imate nakon umetanja, a ne pre.

Dva manja alata upotpunjuju set za pozicioniranje. TXLSTextBox.SetArea na XLS strani ponovo sidri postojeći tekstualni okvir ili auto-oblik na novi pravougaonik ćelije, što je mnogo bolje nego brisanje i ponovno kreiranje kada se blok u podnožju pomeri. A preopterećenje AddPicture za bitmapu prihvata aktivni TBitmap sa opcionom zastavicom transparentnosti, tako da sve što vaš VCL kod može da nacrta (merni instrument, sparkline liniju ili tip grafikona koji matična lista ne nudi) može biti utisnuto direktno na list bez prethodnog snimanja privremenog fajla.

Grafikoni i slike su skoro uvek završni sloj na već strukturiranom izveštaju, zbog čega pripremni radovi odlučuju da li će oni leći kako treba. Popunjavanje podataka na koje će se grafikon pozivati pokriveno je u članku o generisanju izveštaja vođenom šablonima, a održavanje mreže stabilnom ispod vaših sidara je tema članka o spojenim ćelijama i kontroli rasporeda. Kompletna dokumentacija o klasama i metodama nalazi se na stranici proizvoda HotXLS Component.