Ein Chart-Part, den ein aktueller Excel-Build speichert, endet selten bei den Elementen, die das ECMA-376-Chart-Schema definiert. Der Chart Space öffnet gewöhnlich mit einem mc:AlternateContent-Block, der neueren Lesern c14:style anbietet und älteren ein schlichtes c:style, jede Serie trägt ein c:extLst mit einer c16:uniqueId, und "Value From Cells"-Datenbeschriftungen leben in yet einer weiteren Erweiterung. Ein Programm, das nur parst, was es modelliert, und den Part zur Speicherzeit neu erzeugt, schreibt nichts davon zurück. Ein Wort im Titel geändert, die Datei in Excel wieder geöffnet, und die Beschriftungen, die ihren Text aus einem Zellbereich zogen, sind weg
Die Lösung ist, ein Speichern nicht mehr als Neuaufbau zu behandeln. Die Bytes behalten, mit denen das Diagramm ankam, sie unverändert zurückschreiben, wenn sich nichts Modellierte bewegt hat, und wenn doch, die neuen Elemente in den originalen Baum einführen, sodass die unvertrauten Teile bleiben, wo Excel sie hingelegt hat
Das originale ChartML erneut abspielen, wenn nichts geändert hat
HotXLS tut genau das, wenn TXLSXWorkbook.PreserveUnsupportedParts auf True steht, was bei einer frisch erzeugten Arbeitsmappe die Voreinstellung ist. Beim Import behält jedes Diagramm seine originalen UTF-8-Bytes zusammen mit der Länge und einem 64-Bit-Fingerprint des Inhalts, den das typisierte Modell repräsentiert. Zur Speicherzeit erzeugt HotXLS den modellierten Part neu, vergleicht Länge und Fingerprint, und wenn beide übereinstimmen, schreibt es die gespeicherten Bytes wortwörtlich zurück: keine Element-Umsortierung, kein Whitespace-Drift und keine zweite UTF-16-Kopie jedes unangetasteten Diagramms im Speicher
Weil der Vergleich auf dem Inhalt läuft, ändert das Zuweisen eines Werts, den eine Eigenschaft bereits hält, nichts. TXLSXChart-Setter kehren bei gleichem Wert vorzeitig zurück, also spielt eine Schleife, die jeden Diagrammtitel aus einer Datenbankzeile "auffrischt", weiterhin jedes Diagramm ab, dessen Titel bereits korrekt war. Die Linienformatierung von Fehlerbalken und Trendlinien wird von ihrem eigenen Änderungsflag verfolgt, und sie zu ändern zählt immer als Bearbeitung
TXLSXChart.HasPreservedXml verrät, welchen Weg ein Diagramm nimmt, bevor man irgendetwas bearbeitet. Es meldet True, wenn dem Diagramm originales ChartML anhaftet, was die Vorbedingung für Replay und Zusammenführung gleichermaßen ist
var
Workbook: TXLSXWorkbook;
Chart: TXLSXChart;
begin
Workbook := TXLSXWorkbook.Create;
try
// Per Voreinstellung True; explizit setzen, damit die Absicht Refactorings überlebt.
// Es muss vor Open stehen, weil Open den Zustand aufbaut
Workbook.PreserveUnsupportedParts := True;
Workbook.Open('quarterly-review.xlsx');
Chart := Workbook.Sheets[1].Charts[0]; // Sheets[] ist 1-basiert, Charts[] 0-basiert
if not Chart.HasPreservedXml then
Log('chart 0 has no original ChartML attached'); // Log ist die eigene Routine
Chart.Title := Chart.Title; // gleicher Wert: wird weiterhin abgespielt
Workbook.Sheets[1].Charts[1].Title :=
'Revenue by region, Q3 2026'; // dieses Diagramm nimmt den Zusammenführungspfad
Workbook.SaveAs('quarterly-review-out.xlsx');
finally
Workbook.Free;
end;
end;
Was eine Zusammenführung behält, wenn man ein Diagramm bearbeitet?
Die kurze Antwort: alles, was das typisierte Modell nicht besitzt. Titel, Serien, Daten-Caches, Plot-Gruppen und Achsen gehören dem Modell, also entscheiden sie der neu erzeugte Baum. Nicht modellierte Attribute, Namespace-Deklarationen, Styles, Erweiterungslisten und Formatierungsknoten kommen aus dem originalen Baum und werden neben den passenden Knoten wieder eingefügt, neben denen sie saßen
Löscht man eine Serie, darf die Zusammenführung sie nicht aus dem bewahrten XML auferstehen lassen, nur weil das Original an derselben Position noch ein c:ser enthält. Alles, was dem Modell gehört, kommt aus dem Modell, selbst wenn die Antwort des Modells "weg" lautet. Dasselbe gilt auf Attributebene: Wischen Sie Fett, Kursiv, Unterstreichung, Durchgestrichen oder die Schriftgröße eines bekannten Textstils ab, liest die Zusammenführung das fehlende Attribut als Zurücksetzen und kopiert b oder sz nicht aus dem originalen defRPr zurück. Unbekannte Attribute desselben Elements behalten ihren Platz
Titel folgen derselben Regel. Beim Import behält HotXLS die nativen Rich-Runs von c:title mit ihren Schriftarten, Größen und Sprach-Tags pro Run und gibt sie erneut aus, solange der Text unangetastet ist. Weist man einen neuen Title zu, wird dieser Rich-Inhalt verworfen; der Titel wird als ein Run neu geschrieben, formatiert aus TitleTextStyle. Alte Runs in neuen Text einzuführen hieße raten, welcher Run zu den neuen Worten gehört, und ein Rateergebnis hier produziert Titel, die niemand geschrieben hat
Die Schema-Reihenfolge bleibt dort erhalten, wo Office pingelig ist. Ein extLst bleibt das letzte Kind seines Elternelements, wie das Schema verlangt. Markup-Compatibility-Blöcke bekommen eine eigene Behandlung: Der Reader überspringt mc:Choice und liest mc:Fallback, und wenn dieser Fallback eine Plot-Gruppe umschließt, die das Modell kennt, schreibt die Zusammenführung die neuen typisierten Inhalte in den Fallback-Teilbaum zurück. Eine naive Zusammenführung würde die bearbeitete Plot-Gruppe ausgeben und die alte Kopie im Fallback-Zweig lassen, also trüge das gespeicherte Diagramm dieselbe Gruppe doppelt
Ein einzelnes Diagramm per ClearPreservedXml ausnehmen
TXLSXChart.ClearPreservedXml verwirft das originale XML und den Fingerprint eines einzelnen Diagramms, und der nächste Save baut dieses Diagramm rein aus dem typisierten Modell neu auf. Es ist eine Entscheidung pro Diagramm. PreserveUnsupportedParts steuert weiterhin, ob beim Import überhaupt ein Erhaltungszustand aufgebaut wird, und es umfasst auch andere nicht unterstützte Paketparts, nicht nur Diagramme – deshalb ist das Abschalten, um ein einziges sauberes Diagramm zu bekommen, der falsche Hebel
Räumt man ein Diagramm aus, wenn das Wegwerfen der Sinn der Operation ist, etwa bei einer Vorlage, die noch Formatierung eines ausgedienten Firmen-Themes trägt. Als Debug-Reflex es allein lassen. Ein Diagramm, das nach einer Bearbeitung falsch aussieht, ist meist eine Modellierungsfrage, und das Löschen des bewahrten XML wirft die einzige Kopie davon weg, was Excel ursprünglich geschrieben hat
Ein Fall braucht vorher eine Prüfung. Nutzt ein Fehlerbalken oder eine Trendlinie eine Linienformatierung, die das typisierte Modell nicht darstellen kann, ist ihre Eigenschaft LineAppearanceSupported False; eine Trendlinien-Beschriftung mit Formatierung außerhalb des Modells meldet dasselbe über LabelAppearanceSupported. HotXLS ersetzt keines von beiden stillschweigend durch eine schlichtere Variante: Sobald das Diagramm kein bewahrtes XML als Rückfallebene hat, stoppt der Save mit einer EArgumentException, die die nicht darstellbare Linien- oder Beschriftungserscheinung benennt. Beides vor dem Ausnehmen zu prüfen kostet eine Schleife
// Eigener Helfer, kein Teil von HotXLS: True, wenn jede Fehlerbalken-Linie,
// Trendlinien-Linie und Trendlinien-Beschriftung allein aus dem Modell schreibbar sind
function ChartRebuildable(Chart: TXLSXChart): Boolean;
var
I, T: Integer;
S: TXLSXChartSeries;
begin
Result := False;
for I := 0 to Chart.SeriesCount - 1 do // läuft über jede Plot-Gruppe
begin
S := Chart.Series[I];
if (S.ErrBarX <> nil) and not S.ErrBarX.LineAppearanceSupported then Exit;
if (S.ErrBarY <> nil) and not S.ErrBarY.LineAppearanceSupported then Exit;
for T := 0 to S.TrendlineCount - 1 do
begin
if not S.Trendlines[T].LineAppearanceSupported then Exit;
if not S.Trendlines[T].LabelAppearanceSupported then Exit;
end;
end;
Result := True;
end;
// ...
Chart := Workbook.Sheets[1].Charts.FindByName('Old theme chart');
if (Chart <> nil) and ChartRebuildable(Chart) then
Chart.ClearPreservedXml // der nächste Save baut dieses Diagramm aus dem Modell neu auf
else
Log('kept original ChartML for this chart');
Kombinationsdiagramme mit AddPlotGroup und einer zweiten Achse
TXLSXChart.AddPlotGroup hängt eine geordnete Plot-Gruppe mit eigener Diagrammfamilie und eigener primärer oder sekundärer Achsenzuordnung an. Das deckt das Diagramm ab, das jedes Finanzteam verlangt: Umsatz als Säulen auf der linken Achse, Marge als Prozentlinie rechts. Jedes Diagramm exponiert ValueAxis, CategoryAxis, SecondaryValueAxis, SecondaryCategoryAxis und SeriesAxis, mit typisierten Einstellungen für Position, Achsen-Identifikatoren, Schnitt, Datumseinheiten, Beschriftungsintervalle, Zahlenformat und Sichtbarkeit
Die flache API aus Eintyp-Diagrammen ist weiterhin da und mappt auf die Gruppen. ChartType und AddSeries adressieren die erste Plot-Gruppe; SeriesCount und Series[] laufen die Serien aller Gruppen der Reihe nach ab, weshalb der Helfer oben auch die Linienserie sieht. Die Reihenfolge zählt an einer Stelle. TXLSXCharts.Add erzeugt ein Diagramm ohne jede Plot-Gruppe, und welches von AddSeries oder AddPlotGroup zuerst läuft, legt Gruppe eins an. Auf einem frischen Diagramm zuerst AddPlotGroup(xlsxChartLine, xlsxAxisSecondary) aufzurufen macht die Linie zu Gruppe eins, der Diagrammtyp wechselt zu Linie, und die Umsatzserie, die man danach hinzufügt, landet darin. Beide Gruppen explizit anzulegen nimmt die Mehrdeutigkeit, also tut das Beispiel genau das
var
Chart: TXLSXChart;
Columns, Line: TXLSXChartPlotGroup;
begin
// Verankert über Zeilen 1..15, Spalten 5..12; noch keine Plot-Gruppe vorhanden
Chart := Sheet.Charts.Add(xlsxChartColumn, 'Revenue and margin',
1, 5, 15, 12);
Columns := Chart.AddPlotGroup(xlsxChartColumn); // Gruppe 1, primär
Columns.AddSeries('Revenue', 'Data!$A$2:$A$13', 'Data!$B$2:$B$13');
Line := Chart.AddPlotGroup(xlsxChartLine, xlsxAxisSecondary); // Gruppe 2
Line.AddSeries('Margin %', 'Data!$A$2:$A$13', 'Data!$C$2:$C$13');
Line.ZOrder := 1; // die Linie vor den Säulen zeichnen
Chart.SecondaryValueAxis.NumberFormat := '0%';
Chart.SecondaryValueAxis.NumberFormatSourceLinked := False;
Chart.SecondaryValueAxis.MinValue := 0;
Chart.SecondaryValueAxis.MaxValue := 0.5;
// Plot-Area als Bruchteile des Diagramms: x, y, Breite, Höhe in einem Aufruf
Chart.PlotAreaLayout.SetPositionAndSize(0.08, 0.12, 0.80, 0.72);
end;
ZOrder einer Plot-Gruppe steuert die Stapelung: Der Chart-Writer sortiert die Gruppen danach, bevor er die c:barChart- und c:lineChart-Blöcke ausgibt, niedrigere Werte hinten. Weil jede geordnete Gruppe modelliert ist, behält das Öffnen und erneute Speichern eines Kombinationsdiagramms jede Diagrammfamilie zusammen mit ihren Sekundär-, Datums- und Serienachsen. Chart-Formelreferenzen und gecachte Serienwerte überleben Arbeitsmappen-Kopien und Open/Save-Zyklen, und Chart-Anker werden über den Relationship-Identifikator ihrem Chart-Part zugeordnet, sodass ein Paket, dessen Relationship-Einträge von einem anderen Tool umsortiert wurden, jedes Diagramm trotzdem an seiner eigenen Arbeitsblattposition platziert
Warum ignoriert Office ein manuelles Layout?
Weil die Koordinaten unvollständig ankamen. Office verlangt x, y, w und h als vollständige Gruppe, sobald auch nur eines davon in einem c:manualLayout auftaucht, und ignoriert andernfalls das gesamte Element. Das Schema markiert alle vier als optional, also ist eine Datei mit nur x und y gültig und macht in Excel trotzdem nichts
TXLSXChartLayout.SetPositionAndSize ist der Aufruf dafür. Er setzt alle vier Werte, markiert die vier Mode-Elemente als vorhanden und schaltet das Layout in einem Schritt frei. Die einzelnen Eigenschaften X, Y, Width und Height setzen jeweils einen Wert, und HotXLS schreibt exakt die Elemente, die vorhanden sind, genauso wie es beim Lesen einer Datei die Anwesenheit jedes der neun optionalen Kinder festhält. So bleibt ein unangetastetes Layout treu zu seiner Quelle. Es bedeutet auch, dass ein Layout aus zwei Eigenschaftszuweisungen mit zwei Koordinaten geschrieben wird und Office es überspringt. Dieselbe Klasse TXLSXChartLayout steckt hinter PlotAreaLayout, TitleLayout, LegendLayout und dem TitleLayout jeder Achse
Zwei Voreinstellungen folgen dem, was Excel tut, statt allem, was die Spezifikation erlaubt. Ein neues Layout nutzt edge für XMode und YMode und factor für WidthMode und HeightMode; der Standard erlaubt die anderen Kombinationen, aber Excel speichert Positionen als Kanten und Größen als Faktoren und konvertiert alles andere beim eigenen Speichern. Und layoutTarget (Target, innen oder außen) wird für die Plot-Area geschrieben, während Titel-, Achsentitel- und Legend-Layouts es weglassen, denn die Implementierungsnotizen von Microsoft zum Chart-Schema erlauben es unter diesen Elementen nicht
Das in einen Arbeitsmappen-Roundtrip einpassen
Diagramm-Erhaltung ist ein Fall einer allgemeineren Regel: Eine Bibliothek, die woanders erstellte Dokumente bearbeitet, sollte das ändern, was sie ändern soll, und den Rest so lassen, wie sie ihn vorgefunden hat. Denselben Gedanken wendet HotXLS bei Themes, Berechnungsketten und Arbeitsblatt-Erweiterungen an, beschrieben in verlustfreie Roundtrips für Theme, extLst und calcChain, und bei VBA-Projekten und externen Links in Erhaltung von VBA und externen Links. Für Diagramme, Bilder und Zeichnungen von Grund auf siehe Diagramme, Bilder und Zeichnungen in HotXLS
HotXLS liest, bearbeitet und schreibt XLSX-Diagramme aus nativem Delphi- und C++Builder-Code, ohne dass Excel auf der Maschine installiert ist, also wird die Diagrammtreue von der Bibliothek entschieden und nicht von einem Automatisierungsserver. Die Diagramm-API-Referenz und ein Trial-Build finden sich auf der HotXLS-Delphi-Spreadsheet-Komponentenseite