Article technique

Objets graphiques embarqués dans les feuilles avec HotXLS

HotXLS peut placer un graphique directement sur une feuille de calcul, ancré à une plage de cellules, au lieu de le mettre sur une feuille de graphique dédiée. En termes BIFF8, cela signifie écrire une forme de dessin avec un enregistrement OBJ de type 5 et garer le sous-flux de graphique à la fin du flux d'enregistrements de la feuille, ce qui est exactement la disposition qu'Excel produit et exactement là où le lecteur s'attend à le trouver

La distinction importe à quiconque génère des rapports opérationnels. Une feuille de graphique est un bon logis pour un visuel de titres unique. Un détail mensuel par région veut le graphique à côté des nombres qu'il résume, sur la même feuille, dimensionné pour le bloc de cellules auquel il appartient, si bien que le lecteur défile une fois au lieu de changer d'onglet et de perdre le contexte

La lecture était déjà là, l'écriture non

L'asymétrie vaut la peine d'être nommée parce qu'elle façonne le travail. HotXLS savait déjà lire les graphiques embarqués : quand le flux d'enregistrements de la feuille contient un BOF marqué comme sous-flux de graphique, l'analyseur change de contexte, collecte les enregistrements de graphique, et au EOF de fermeture les rend à la forme de dessin que l'enregistrement OBJ avait introduite. Ce chemin avait été exercé par chaque classeur créé par Excel que la bibliothèque avait jamais ouvert

Ce qui manquait était le côté rédaction, et la conséquence utile est que le nouveau rédacteur avait une spécification précise à atteindre : produire la disposition d'octets que le lecteur existant rattache déjà. Il n'est pas de meilleur critère d'acceptation pour une fonctionnalité de format binaire qu'un lecteur écrit indépendamment que vous n'aviez pas le droit de modifier

De quoi un graphique embarqué est-il fait ?

Trois pièces doivent concorder. La couche de dessin fournit une forme de contrôle hôte, la couche objet fournit un enregistrement OBJ dont les données d'objet communes déclarent le type d'objet 5, et le flux d'enregistrements fournit le sous-flux de graphique lui-même. Les drapeaux d'option de l'enregistrement OBJ sont ceux qu'Excel écrit pour un cadre de graphique : positionné, verrouillé, ligne automatique et remplissage automatique, et c'est ce qui fait qu'un graphique embarqué se comporte comme un natif quand un utilisateur clique dessus

HotXLS ancre un sous-flux de graphique BIFF8 à une feuille Delphi à travers trois pièces concordantes : la forme de contrôle hôte de la couche de dessin, l'enregistrement OBJ dont les données d'objet communes déclarent le type d'objet 5, et la chaîne d'enregistrements de graphique garée à la fin du flux d'enregistrements de la feuille, où un BOF de graphique change le contexte de l'analyseur et le EOF de fermeture rattache les enregistrements
Trois couches portent un seul graphique embarqué : la forme de dessin l'ancre, l'enregistrement OBJ le type comme hôte de graphique, et le sous-flux de graphique en fin de flux de feuille fournit les enregistrements que le lecteur rattache

L'ancre mérite une remarque parce qu'elle est une source courante de bugs de décalage de un. L'API HotXLS prend des numéros de ligne et de colonne à base un, comme le reste de la bibliothèque, et l'ancre cliente écrite dans le fichier est à base zéro. La conversion se passe à l'intérieur de AddChartObject, si bien que les appelants restent dans le système de coordonnées qu'ils utilisent partout ailleurs, mais quiconque compare un dump hexadécimal à son propre appel doit se rappeler de quel côté de cette frontière il lit

var
  Book: TXLSWorkbook;
  Sheet: TXLSWorksheet;
  Series: array[0..1] of TXLSChartSeriesInfo;
begin
  Book := TXLSWorkbook.Create(nil);
  try
    Book.LoadFromFile('regional-sales.xls');
    Sheet := Book.Sheets[0];

    FillChar(Series, SizeOf(Series), 0);
    Series[0].Name := 'Actual';
    Series[0].Categories := 'Data!$A$2:$A$13';
    Series[0].Values := 'Data!$B$2:$B$13';
    Series[0].DataLabels.ShowValue := True;
    Series[0].HasDataLabels := True;

    Series[1].Name := 'Target';
    Series[1].Categories := 'Data!$A$2:$A$13';
    Series[1].Values := 'Data!$C$2:$C$13';
    Series[1].SecondaryAxis := True;

    // Ancré à E2:M20 sur cette feuille, à base un
    Sheet.AddChartObject(xlsChartTypeColumn, 'Regional sales',
      'Month', 'Amount', Series, 2, 5, 20, 13);

    Book.SaveToFile('regional-sales-charted.xls');
  finally
    Book.Free;
  end;
end;

Le FillChar sur le tableau de séries n'est pas décoratif. TXLSChartSeriesInfo transporte plusieurs sous-enregistrements optionnels, étiquettes de données, style par série, courbes de tendance et barres d'erreur, chacun gardé par un booléen, et un enregistrement partiellement initialisé sur la pile remettra à l'émetteur des drapeaux que personne n'a posés. Mettez le tableau à zéro, puis posez les champs voulus

Quelles références de séries le chemin embarqué accepte-t-il ?

Des plages simples de style A1 à l'intérieur du même classeur, et cette restriction est délibérée plutôt qu'un oubli. Chaque référence est résolue contre la liste des feuilles du classeur et transformée en index de référence externe dont les enregistrements de graphique ont besoin. Une plage nommée ou une référence à un classeur externe retombe sur un espace réservé avec une expression analysée de longueur nulle, si bien que le graphique s'écrit proprement mais que cette série particulière n'a pas de source de données tant que vous ne la pointez pas vers une plage

Acceptation des références de séries HotXLS sur le chemin de graphique BIFF8 embarqué : les plages de style A1 telles que Data!$B$2:$B$13 à l'intérieur du même classeur se résolvent contre la liste des feuilles en l'index de référence externe dont les enregistrements de graphique ont besoin, tandis que les plages nommées et les références à des classeurs externes retombent sur un espace réservé avec une expression analysée de longueur nulle, les deux couverts par AddChartSheet
Seules les plages simples de style A1 dans le même classeur se compilent en références de séries de graphique ; tout le reste s'écrit proprement comme un espace réservé jusqu'au repostage, et le chemin complet vit sur AddChartSheet

La raison est un arbitrage d'ingénierie direct. Le chemin complet de compilation de références existe sur la route des feuilles de graphique, emballé dans la couche de collection de feuilles, et le sortir proprement signifierait dupliquer une centaine de lignes de logique de résolution pour un cas rare en pratique. Un graphique embarqué trace presque toujours des cellules de sa propre feuille ou d'une feuille de données voisine. Les références nommées et externes sont couvertes sur le chemin des feuilles de graphique via AddChartSheet, donc rien n'est indisponible, juste atteint depuis un autre point d'entrée

Tout le reste du modèle de séries fonctionne à l'identique sur les deux routes. La liaison d'axe secondaire, les styles de ligne, remplissage et marqueur par série, les courbes de tendance, les barres d'erreur et les étiquettes de données font tous partie de TXLSChartSeriesInfo et s'émettent tous de la même façon, si bien qu'une définition de graphique peut passer d'un objet embarqué à une feuille de graphique avec seulement l'appel qui change. La mécanique des groupes d'axes derrière le drapeau d'axe secondaire est couverte dans les groupes d'axes secondaires à l'écriture BIFF

Pourquoi le titre du graphique se lisait-il en deux caractères ?

Parce qu'un compte de caractères a été passé là où un compte d'octets était attendu, et que les chaînes Unicode BIFF rendent cette erreur facile à écrire et difficile à voir. Une chaîne Unicode BIFF courte commence par un compte de caractères et un octet de drapeaux, et l'octet de drapeaux porte le bit de poids haut qui dit si la charge utile est d'un ou de deux octets par caractère. Lisez une charge utile de 16 bits avec le compte de caractères comme si c'était une longueur en octets et vous obtenez exactement la moitié de la chaîne : une série nommée Sales revient en Sa, et un titre de graphique se tronque pareillement parce que titres et étiquettes de séries partagent le chemin de décodage

Ce qui rend ce défaut notable est qu'il est revenu trois fois dans la même famille d'enregistrements, une fois dans les noms de courbes de tendance, une fois dans les noms de graphiques croisés dynamiques, et une fois dans les titres de graphique. Chaque occurrence ressemblait à un bug neuf dans une fonctionnalité neuve. Les trois étaient le même multiply manquant. La règle qui l'a enfin clos est mécanique et devrait être appliquée sans jugement : chaque fois que vous lisez une de ces chaînes, consultez d'abord le drapeau de poids haut et multipliez le compte de caractères par la largeur de charge utile avant de toucher au tampon. Les détails au niveau des enregistrements sont dans le décodage des comptes de caractères XLUnicodeString et du drapeau de poids haut

// Le graphique embarqué partage la couche de dessin avec les images et
// les formes, donc un dessin existant sur la feuille est préservé.
// AddChartObject renvoie l'index de l'objet créé
var
  ObjIndex: Integer;
begin
  ObjIndex := Sheet.AddChartObject(xlsChartTypeLine, 'Trend',
    'Week', 'Units', Series, 2, 8, 18, 16);
  if ObjIndex < 0 then
    raise Exception.Create('chart object was not created');
end;

Où les graphiques embarqués se situent face aux alternatives

Trois routes existent et répondent à des questions différentes. Un objet graphique embarqué appartient à côté de ses données sur une feuille et c'est ce que veulent la plupart des rapports. Une feuille de graphique convient à un visuel de présentation unique et vous donne le chemin complet de compilation de références. Préserver tel quel un graphique existant d'un fichier chargé est la bonne réponse quand le classeur vient d'Excel avec un formatage que personne ne veut voir une bibliothèque réinterpréter ; ce comportement de pass-through est décrit dans les graphiques ChartML préservés et les graphiques combinés

Comme le graphique embarqué voyage sur la couche de dessin, il coexiste avec les images et les formes de la même feuille au lieu de les remplacer, et le modèle général de cette couche est couvert dans les graphiques, images et dessins dans HotXLS. Les trois routes sont livrées avec le composant tableur Delphi HotXLS, si bien que le choix porte sur l'allure que le rapport doit avoir plutôt que sur ce que la bibliothèque peut exprimer

Le point méthodologique est celui qui vaut d'être gardé. Quand une fonctionnalité de format binaire a un lecteur existant, construisez le rédacteur contre le lecteur plutôt que contre votre lecture de la spécification. Le lecteur encode des années de contact avec les fichiers que de vraies applications ont réellement produits, y compris les parties que la spécification énonce vaguement, et un rédacteur qui le satisfait a bien plus de chances de satisfaire Excel aussi