Article technique

Graphiques, images et objets de dessin HotXLS en Delphi

Le ticket de support disait que le graphique manquait. Un service Delphi générait un rapport .xls mensuel, créait une feuille graphique pour la tendance du chiffre d'affaires, puis essayait d'écrire une légende de données sur cette même feuille avec des appels de cellules. Aucune exception n'était levée pendant la génération, mais Excel supprimait silencieusement le flux de dessin incohérent à l'ouverture. La cause racine est un détail de format facile à rater : en BIFF8, une feuille graphique est un sous-flux séparé, pas une feuille de calcul avec un graphique flottant dessus, et les opérations au niveau cellule y sont non prises en charge. HotXLS, bibliothèque Object Pascal native qui lit et écrit XLS et XLSX sans automatisation Excel, expose les deux modèles de dessin — mais ce sont vraiment deux modèles différents, et la plupart des incidents de production sur les graphiques et images viennent de l'application des règles d'un format à l'autre.

Deux formats de fichier, deux modèles de dessin

Avant d'écrire le moindre code de graphique, décidez quel conteneur vous produisez, car les types d'objets disponibles diffèrent :

  • XLS (BIFF8) : les graphiques vivent sur des feuilles graphiques dédiées créées via AddChartSheet sur la collection Sheets. Les images, zones de texte, rectangles, ovales et lignes sont des formes OfficeArt gérées par la collection Shapes de la feuille de calcul. Il n'existe pas d'API pour intégrer un graphique dans une grille de feuille normale.
  • XLSX (OOXML) : les graphiques peuvent être intégrés directement dans une feuille avec TXLSXWorksheet.AddChart, ancrés à un rectangle de cellules, ou placés sur une feuille graphique dédiée avec TXLSXWorkbook.AddChartSheet. Les images s'ajoutent avec AddImage ou AddImageFromFile, et les libellés flottants avec AddTextBox.

Si l'exigence est « une feuille tableau de bord avec le graphique à côté des chiffres », le livrable est XLSX. Essayer de le satisfaire en .xls impose une feuille graphique séparée, ce qui change la navigation pour l'utilisateur et change votre code : la feuille renvoyée côté XLS par AddChartSheet doit être traitée comme strictement graphique et ne jamais recevoir d'écriture via Cells.Item. Cette seule règle aurait évité l'incident ci-dessus.

Intégrer un graphique dans une feuille XLSX

Le chemin XLSX est le plus flexible, donc il vaut la peine de clarifier les deux systèmes de coordonnées. Le rectangle d'ancrage passé à AddChart s'exprime en lignes et colonnes de feuille et définit où se place le cadre du graphique ; les données de série s'expriment en références A1 absolues incluant le nom de feuille. Les deux sont indépendants — déplacer le cadre du graphique ne change jamais ce qu'il trace.

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;

La ligne qui se trompe le plus souvent est AddSeries. Les arguments de catégories et de valeurs sont des chaînes de plages littérales ; ils ne grandissent donc pas lorsque d'autres lignes sont ajoutées plus tard dans l'exécution. Écrivez les données d'abord, suivez le nombre final de lignes, puis seulement construisez la référence de série à partir de ce nombre. Pour les graphiques en nuage de points et à bulles, le même appel change de sémantique : la plage de catégories fournit les valeurs X et la plage de valeurs fournit Y, les tailles de bulles étant définies séparément via BubbleSizeRange sur le TXLSXChartSeries renvoyé.

Les valeurs TXLSXChartType disponibles couvrent les histogrammes, barres, lignes, secteurs, aires, anneaux, nuages de points, bulles et radars. Lorsque le livrable est un graphique pleine page, Book.AddChartSheet crée une feuille dont la propriété IsChartSheet vaut true — l'équivalent XLSX de la feuille graphique héritée, avec la même attente « pas de contenu de grille ».

Les images sont des octets, et les tailles sont des EMU

Le bug d'image le plus courant en revue de code consiste à passer un chemin de fichier au mauvais overload. AddImage(ARow, ACol, AData, AFormat) prend les octets d'image déjà encodés — données PNG, JPEG, GIF ou BMP brutes — dans son paramètre AData. Donnez-lui une chaîne de chemin et vous avez inséré une « image » de quarante octets qu'aucun visualiseur ne peut décoder. Lorsque la source est un fichier sur disque, utilisez AddImageFromFile et laissez la bibliothèque la charger et la classifier.

Le dimensionnement piège ensuite. DrawingML mesure en English Metric Units : 914400 EMU par pouce, soit 9525 EMU par pixel à 96 DPI. L'objet TXLSXImage expose WidthEMU et HeightEMU ; un logo qui doit se rendre à 180 par 60 pixels demande donc 1714500 par 571500 EMU. Faites cette arithmétique une fois dans une constante helper plutôt que de disperser des nombres magiques, et souvenez-vous que la ligne et la colonne d'ancrage sont 1-based comme le reste de l'API cellule.

Feuilles graphiques et formes dans les fichiers XLS hérités

Côté BIFF8, l'overload plus riche de AddChartSheet accepte le type de graphique, les titres et un tableau ouvert de records TXLSChartSeriesInfo, chacun portant nom, catégories et valeurs sous forme de chaînes de plages. Les formes sont ajoutées sur la feuille de données elle-même :

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;

Deux détails de durée de vie comptent ici. TXLSWorkbook est détenu via l'interface IXLSWorkbook et est compté par références, donc le libérer manuellement provoque une double libération ; c'est l'inverse de TXLSXWorkbook, qui doit être libéré dans un bloc try..finally. Et les helpers de formes — AddRectangle, AddOval, AddLine, plus DeleteInRange pour nettoyer une région de dessins — s'ancrent tous par paires ligne/colonne, de sorte qu'un modèle qui insère des lignes au-dessus des formes les déplacera avec la grille.

TXLSPicture prend en outre en charge TransparentColor pour masquer une couleur de fond dans les bitmaps, ce qui est le chemin pratique pour poser un tampon non rectangulaire sur du contenu de grille dans un format antérieur à la prise en charge de l'alpha PNG dans le rendu BIFF.

Les couleurs de thème ne survivent pas à un aller-retour BIFF8

Les remplissages DrawingML OOXML peuvent référencer un emplacement de couleur de thème ; recolorer tout un document en remplaçant le thème est donc peu coûteux en .xlsx. Les records de dessin BIFF8 n'ont aucun emplacement équivalent. Lorsque HotXLS applique une couleur de thème à un dessin XLS, il résout et stocke la valeur RGB finale ; après enregistrement et réouverture du fichier, l'index de thème d'origine n'existe tout simplement plus à relire. Si votre produit rebrande des documents générés — un outil de reporting en marque blanche, par exemple — conservez la correspondance thème vers RGB dans la configuration applicative et réappliquez-la à la génération, au lieu d'espérer la récupérer depuis un .xls enregistré.

Un compromis voisin apparaît côté performance : la façade XLS peut ignorer l'analyse de toute la couche de dessin lorsque vous n'avez besoin que des données de cellules de grands fichiers hérités, ce qui accélère nettement les lectures en masse, mais un classeur chargé ainsi ne doit jamais être enregistré, parce que le flux OfficeArt ignoré a disparu. Cette technique appartient aux jobs analytiques en lecture seule, détaillés dans nos notes sur la performance des grands classeurs dans HotXLS.

Garder les ancrages stables pendant que la grille change

Les rapports restent rarement à la taille à laquelle ils ont été générés. Les opérations structurelles de la façade XLSX — InsertRows, DeleteRows et leurs équivalents de colonnes — déplacent les couches dépendantes avec les cellules : régions fusionnées, liens hypertexte, commentaires, volets figés, plages de filtres, formats conditionnels, validations, tableaux, noms définis et, point important ici, ancrages d'images et de graphiques bougent ensemble. Un logo ancré ligne 1 reste en haut quand dix lignes sont insérées sous lui ; un cadre de graphique ancré sous le bloc de données glisse vers le bas lorsque le bloc grandit. Ce qui n'est pas réécrit, c'est tout ce que vous avez stocké vous-même comme chaîne littérale avant l'insertion ; l'ordre sûr pour remplir un modèle est donc : écrire et remodeler les données d'abord, puis créer les graphiques et placer les images en toute dernière passe, en dérivant chaque chaîne de plage depuis les nombres de lignes après insertion.

Deux outils de placement plus petits complètent l'ensemble. Côté XLS, TXLSTextBox.SetArea repositionne une zone de texte ou une forme automatique sur un nouveau rectangle de cellules après coup, moins coûteux que la suppression puis recréation lorsqu'un bloc de pied de page se déplace. Et l'overload bitmap de AddPicture accepte un TBitmap vivant avec un flag de transparence optionnel ; des graphiques rendus par votre propre code de dessin VCL — jauges, bandes sparklines, tout ce que les types natifs de graphique ne couvrent pas — peuvent donc être tamponnés dans la feuille sans passer par un fichier temporaire.

FAQ : graphiques et images dans HotXLS

Puis-je intégrer un graphique dans une feuille .xls normale ? Non. L'API BIFF8 crée uniquement des feuilles graphiques. Si la mise en page exige un graphique flottant à côté des données, générez du .xlsx avec TXLSXWorksheet.AddChart, ou restructurez le livrable autour d'une feuille graphique.

Pourquoi mon image insérée apparaît-elle comme une icône cassée dans Excel ? Presque toujours parce qu'un chemin de fichier a été passé à AddImage, qui attend des octets image encodés. Passez à AddImageFromFile ou lisez d'abord le fichier dans la chaîne de données.

Comment donner à une image exactement la taille du bitmap d'origine ? Multipliez les dimensions en pixels par 9525 et affectez les résultats à WidthEMU et HeightEMU. Excel redimensionne toute autre valeur selon ce que l'ancrage implique, rarement ce que le designer voulait.

Les graphiques et images arrivent généralement comme couche finale d'un rapport structuré, donc les fondations comptent : la génération de rapports pilotée par modèle couvre le remplissage des données que le graphique référencera, et les cellules fusionnées et le contrôle de mise en page couvrent la stabilité de la grille sous vos ancrages. La documentation complète des classes et méthodes se trouve sur la page produit HotXLS Component.