Odborný článok

Grafy, obrázky a kreslené objekty HotXLS v Delphi

Čokoľvek, čo sa vznáša nad mriežkou pracovného hárka (graf, logo, pečiatka, textová bublina), je kreslený objekt. Kreslený objekt je definovaný dvoma vecami: tým, čím je, a tým, kde je ukotvený. Ukotvenie (anchor) je časť, v ktorej ľudia najčastejšie robia chyby. Graf sa nenachádza v konkrétnej bunke; je umiestnený v obdĺžniku prichytenom k rozsahu riadkov a stĺpcov, pričom údaje, ktoré vykresľuje, sú samostatnou sadou odkazov A1, o ktorých ukotvenie nič nevie. Presuňte rám a vykreslenie zostane na mieste. Vložte pod neho riadky a rám sa posunie s nimi nadol. Udržanie poriadku v týchto dvoch súradnicových systémoch je kľúčom k tomu, aby kód pre kreslenie fungoval správne

HotXLS je natívna knižnica pre Object Pascal, ktorá číta a zapisuje formáty XLS a XLSX bez automatizácie Excelu, a obsahuje dva samostatné modely kreslenia, pretože tieto dva formáty súborov ukladajú kresby odlišne. Formát BIFF8 (.xls) uchováva grafy na ich vlastných vyhradených hárkoch a plávajúce tvary v prúde OfficeArt pripojenom k pracovnému hárku. Formát OOXML (.xlsx) dokáže vložiť graf priamo do mriežky hárka, ukotvený k obdĺžniku buniek, spolu s plávajúcimi obrázkami a tvarmi. Objektový model odráža toto rozdelenie a všetky významné chyby vznikajú práve aplikovaním pravidiel jedného formátu na druhý

Ktorý kontajner čo obsahuje

Výber kontajnera musí predchádzať akémukoľvek kódu grafu, pretože dostupné typy objektov sa v oboch formátoch líšia:

  • XLS (BIFF8): grafy sa nachádzajú na vyhradených hárkoch grafov (chart sheets) vytvorených pomocou AddChartSheet v kolekcii Sheets. Obrázky, textové polia, obdĺžniky, ovály a čiary sú tvary OfficeArt spravované prostredníctvom kolekcie Shapes pracovného hárka. Neexistuje žiadne API pre vloženie grafu priamo do bežnej mriežky pracovného hárka
  • XLSX (OOXML): grafy je možné vložiť priamo do pracovného hárka pomocou TXLSXWorksheet.AddChart, s ukotvením k obdĺžniku buniek, alebo ich umiestniť na vyhradený hárok grafu pomocou TXLSXWorkbook.AddChartSheet. Obrázky sa vkladajú cez AddImage alebo AddImageFromFile a plávajúce popisy pomocou AddTextBox

Požiadavka formulovaná ako „hárok s prehľadom, kde je graf hneď vedľa čísel“ je teda v skutočnosti požiadavkou na formát .xlsx. Vo formáte .xls to môžete priblížiť iba presunutím grafu na samostatný hárok, čo mení spôsob, akým používateľ prechádza súborom, aj spôsob správania vášho kódu. Hárok vrátený metódou AddChartSheet na strane XLS je podprúd grafu (chart substream), nie mriežka: zápis doň pomocou Cells.Item vytvára nekonzistentný prúd kreslenia, ktorý sa síce vygeneruje bez chyby, ale Excel ho pri otvorení zahodí. Graf jednoducho zmizne a v logu zostavenia nie je žiadna zmienka o tom, prečo. Pristupujte k vrátenému hárku ako k hárku určenému výhradne pre graf a vyhnete sa všetkým hláseniam o zmiznutých grafoch

Vloženie grafu do pracovného hárka XLSX

Cesta XLSX poskytuje väčší manévrovací priestor a práve tu sa stávajú konkrétnymi oba súradnicové systémy spomínané v úvode. Kotviaci obdĺžnik (anchor rectangle) odovzdaný metóde AddChart je vyjadrený v riadkoch a stĺpcoch pracovného hárka a určuje pozíciu rámu grafu. Údaje radov (series data) sú vyjadrené ako absolútne odkazy A1 vrátane názvu hárka. Sú nezávislé: môžete presunúť rám na opačnú stranu hárka a graf bude stále vykresľovať rovnaké bunky

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;

Záludným argumentom je reťazec rozsahu odovzdaný metóde AddSeries. Je to literál zachytený v momente volania a nevie o tom, že by ste neskôr mohli pridať ďalších dvadsať riadkov údajov. Zostavte ho na základe počtu riadkov vypočítaného až po zapísaní údajov, nikdy nie pred ním. Bodové (scatter) a bublinové (bubble) grafy preťažujú rovnaké dva argumenty s iným významom: rozsah kategórií teraz dodáva hodnoty X, rozsah hodnôt dodáva Y a polomer bubliny pochádza z tretieho odkazu nastaveného cez BubbleSizeRange na vrátenom objekte TXLSXChartSeries. Keď opustíte rodinu stĺpcových a pruhových grafov, vnímajte toto volanie ako „X, Y, veľkosť“ a nie ako „kategórie, hodnoty“

Typ TXLSXChartType zahŕňa stĺpcové, pruhové, čiarové, koláčové, plošné, prstencové, bodové, bublinové a radarové grafy, čo pokrýva bežný repertoár reportov. Pre graf na celú stránku bez okolitej mriežky metóda Book.AddChartSheet vracia hárok, ktorého vlastnosť IsChartSheet je true. Je to náprotivok staršieho hárka grafu vo formáte .xlsx a spája sa s ním rovnaké očakávanie: nezapisujte doň obsah buniek

Obrázky sa vkladajú ako bajty a ich veľkosť sa udáva v jednotkách EMU

Pre vloženie obrázka existujú dve preťaženia metódy a ich zámena je najčastejšou chybou pri práci s obrázkami odhalenou pri revízii kódu. Metóda AddImage(ARow, ACol, AData, AFormat) vyžaduje už zakódované bajty obrázka v AData: surový obsah súboru PNG, JPEG, GIF alebo BMP. Ak jej odovzdáte cestu k súboru, uložíte iba štyridsaťbajtový reťazec, ktorý žiadny prehliadač nedokáže dekódovať. To vedie k zobrazeniu ikony poškodeného obrázka, čo určite nechcete riešiť po nasadení aplikácie. Ak je zdrojom súbor na disku, zavolajte radšej AddImageFromFile a nechajte knižnicu načítať bajty a určiť formát za vás

Nasleduje určenie veľkosti. Formát DrawingML nemeria v pixeloch; meria v jednotkách English Metric Units (EMU), kde 914400 EMU tvorí jeden palec a pri rozlíšení 96 DPI zodpovedá jeden pixel 9525 EMU. Objekt TXLSXImage poskytuje vlastnosti WidthEMU a HeightEMU, so logo s rozmermi 180 x 60 pixelov vyžaduje veľkosť 1714500 x 571500 EMU. Uložte tento prepočet do pomenovanej konštanty a počítajte s ňou. Magické čísla ako 1714500 roztrúsené v kóde sú nečitateľné a prestanú správne fungovať hneď, ako niekto zmení cieľové rozlíšenie DPI. Kotviaci riadok a stĺpec sú mimochodom indexované od 1, čo zodpovedá zvyšku API pre bunky a nie matematike EMU indexovanej od nuly

Hárky grafov a tvary v starších súboroch XLS

Na strane BIFF8 preťaženie metódy AddChartSheet prijíma typ grafu, názvy osí a otvorené pole záznamov TXLSChartSeriesInfo, kde každý záznam obsahuje názov a rozsahy kategórií a hodnôt ako reťazce. Plávajúce tvary (floating shapes) sú samostatnou záležitosťou: umiestňujú sa na samotný dátový pracovný hárok prostredníctvom jeho kolekcie Shapes, nie na hárok grafu

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;

V tomto smere sú dôležité dva detaily správy životnosti objektov, ktoré idú opačným smerom. Inštancia TXLSWorkbook sa spravuje cez rozhranie IXLSWorkbook s počitatním odkazov, takže ručné volanie Free spôsobí chybu dvojitého uvoľnenia z pamäte. Objekt TXLSXWorkbook z predchádzajúcich častí je však bežný objekt a musí byť uvoľnený v bloku try..finally. Ten istý vývojár, ktorý pri revízii kódu označí chýbajúce volanie Free na strane XLSX, musí označiť prítomnosť tohto volania na strane XLS ako chybu, čo predstavuje reálne riziko pri práci s oboma formátmi v rovnakej jednotke (unit). Samotní pomocníci pre tvary sú jednotní: metódy AddRectangle, AddOval a AddLine spolu s DeleteInRange na vymazanie oblasti kresieb sa všetky kotvia pomocou párov riadkov a stĺpcov, takže šablóna, ktorá nad ne vloží riadky, ich posunie spolu s mriežkou

V starších súboroch má veľký význam ešte jedna vlastnosť. TXLSPicture.TransparentColor maskuje zvolenú farbu pozadia z bitmapy, čo umožňuje umiestniť neobdĺžnikovú pečiatku (napríklad pečať „Schválené“ alebo vodotlač) nad mriežku vo formáte, ktorého BIFF vykresľovanie nepodporuje alfa kanál PNG. Nastavte farbu pozadia pečiatky a obklopujúci obdĺžnik zmizne

Témy farieb neprežijú konverziu do BIFF8 a späť

Výplne kresieb v OOXML môžu odkazovať na pozíciu farby témy, vďaka čomu je zmena farieb celého súboru .xlsx prostredníctvom výmeny témy jednoduchá. Záznamy kresieb v BIFF8 však takúto možnosť nemajú. Keď HotXLS aplikuje farbu témy na kresbu XLS, prepočíta ju na konkrétnu hodnotu RGB a tú uloží. Index témy, z ktorej farba pochádzala, sa zapísaním súboru stráca a opätovné otvorenie ho nedokáže obnoviť. Tento problém sa týka najmä generátorov reportov s možnosťou prispôsobenia dizajnu (white-label), ktoré upravujú vzhľad rovnakého dokumentu pre rôznych zákazníkov. Uchovávajte mapovanie tém na hodnoty RGB vo vlastnej konfigurácii a aplikujte ho pri každom generovaní, namiesto očakávania, že ho načítate späť z uloženého súboru .xls

Súvisiace rozhodnutie sa týka výkonu. Rozhraniu XLS môžete prikázať úplne preskočiť parsovanie grafickej vrstvy, ak z veľkého staršieho súboru potrebujete iba dáta buniek, a to nastavením _DisableGraphics na true, čo výrazne zrýchli hromadné čítanie. Dôsledok je však trvalý: zošit otvorený týmto spôsobom nemá v pamäti prúd OfficeArt, takže jeho uložením sa kresby nenávratne vymažú. Vyhraďte si tento príznak iba pre analytické úlohy určené na čítanie. Širší prehľad o výkone nájdete v našich poznámkach k výkonu pri veľkých zošitoch v HotXLS

Udržiavanie stability ukotvení pri zmenách mriežky

Veľkosť reportov málokedy zostáva nezmenená po ich vygenerovaní a práve tu sa prejavujú výhody modelu ukotvenia spomínaného v úvode. Štrukturálne operácie rozhrania XLSX (ako InsertRows, DeleteRows a ich stĺpcové ekvivalenty) presúvajú závislé vrstvy spolu s bunkami. Zlúčené oblasti, hyperlinky, komentáre, ukotvené priečky, rozsahy filtrov, podmienené formáty, validácie, tabuľky, definované názvy a v tejto súvislosti aj ukotvenia obrázkov a grafov sa presúvajú spoločne. Logo ukotvené na riadku 1 zostane na vrchu, aj keď pod neho vložíte desať nových riadkov. Rám grafu ukotvený pod dátovým blokom sa posunie nadol, keď sa dátový blok zväčší. Jedinou vecou, ktorá sa neprepíše, je akýkoľvek reťazec rozsahu, ktorý ste zachytili ako literál pred vložením riadkov, pretože ide iba o text, ktorý knižnica nemá dôvod kontrolovať. To definuje bezpečné poradie krokov pre napĺňanie šablóny: najprv zapíšte a upravte údaje, a až v poslednom kroku vytvorte grafy a umiestnite obrázky, pričom každý reťazec rozsahu odvoďte od počtu riadkov po vykonaní všetkých úprav a nie pred nimi

Umiestňovaciu sadu dopĺňajú dva menšie nástroje. Metóda TXLSTextBox.SetArea na strane XLS preukotví existujúce textové pole alebo automatický tvar do nového obdĺžnika buniek, čo je lepšie než ich mazanie a opätovné vytváranie pri posune päty. A preťaženie metódy AddPicture pre bitmapu prijíma objekt TBitmap s voliteľným príznakom priehľadnosti, takže čokoľvek, čo dokáže nakresliť váš vlastný VCL kód (napríklad ukazovateľ, minigraf alebo typ grafu, ktorý natívny zoznam neponúka), môžete vložiť priamo do hárka bez nutnosti predošlého ukladania do dočasného súboru

Grafy a obrázky sú takmer vždy záverečnou vrstvou už štruktúrovaného reportu, a preto o ich správnom umiestnení rozhoduje správna príprava podkladu. Napĺňanie údajov, na ktoré bude graf odkazovať, je popísané v článku generovanie reportov riadené šablónami a udržiavanie stabilnej mriežky pod vašimi ukotveniami je témou článku zlúčené bunky a riadenie rozloženia. Kompletná dokumentácia tried a metód sa nachádza na produktovej stránke komponentu HotXLS