HotXLS schrijft Excel waterfall- en treemap-grafieken als zelfstandige cx:chartSpace-parts — xl/charts/chartExN.xml met het content type application/vnd.ms-office.chartex+xml — en anker ze via een mc:AlternateContent-wrapper zodat Excel 2016 en later de grafiek renderen terwijl oudere readers een fallback-vorm krijgen. Kies xlsxChartWaterfall of xlsxChartTreemap en de hele part-pipeline volgt vanzelf
De manier waarop dit probleem zich meestal aankondigt is een supportticket. Een klant stuurt een workbook dat zijn financiële team in Excel 2019 heeft gebouwd, jouw reporting-tool opent het, slaat het op, en de waterfall-grafiek is weg. Geen exceptie, geen waarschuwing, niets in de log — de grafiek zit gewoon niet in de output. Ga je ernaar zoeken in het klassieke chart-codepad, dan zoek je lang, want de grafiek is er nooit geweest
Waarom zitten waterfall- en treemap-grafieken niet in c:chartSpace?
Omdat Microsoft ze er niet kwijt kon. Het DrawingML-chartschema in ECMA-376 Part 1 §21.2 somt zijn chart-elementen op — c:barChart, c:lineChart, c:pieChart en de rest — als een gesloten choice, dus waterfall, treemap, sunburst en funnel toevoegen in Excel 2016 zou betekenen dat een schema breekt waar elke bestaande reader van afhangt. De nieuwe types gingen in plaats daarvan naar een parallel document: een cx:chartSpace-part in de chartex-namespace van 2014, http://schemas.microsoft.com/office/drawing/2014/chartex, opgeslagen naast de klassieke grafieken maar structureel niet aan hen gerelateerd. De drawing-kant naait de twee werelden aan elkaar met mc:AlternateContent, het compatibiliteitsmechanisme uit ECMA-376 Part 3: de mc:Choice-branch declareert Requires="cx1" en bevat een xdr:graphicFrame waarvan a:graphicData/@uri de chartex-namespace is en de payload een kale <cx:chart r:id="rIdN"/>-referentie, terwijl mc:Fallback een rechthoek houdt met de notice die Excel er zelf in schrijft. Een reader die cx1 niet begrijpt is volgens het Part 3-contract verplicht de fallback te nemen
Het relationship-type is de andere weggever. Een klassieke grafiek hangt aan de drawing-part met de standaard OOXML-chartrelationship; een chartEx-part gebruikt in plaats daarvan http://schemas.microsoft.com/office/2014/relationships/chartEx, gedocumenteerd in [MS-ODRAWXML]. HotXLS stelt beide werelden als constanten beschikbaar — XlsxRtChartEx voor die relationship, XlsxCtChartEx voor het content type, XlsxNsMsChartEx en XlsxNsMsChartEx2015 voor de twee namespaces die het anchor nodig heeft — en het predicaat XlsxChartIsChartEx is wat een grafiek bij het opslaan het verlengde pad in stuurt. Combinatiegrafieken en bewaarde klassieke chart-XML gaan het andere pad in, beschreven in het artikel over bewaarde chart-XML en combinatiegrafieken
Vier schemaregels die bepalen of Excel het bestand opent
Het chartex-schema is op vier specifieke plekken strenger dan het klassieke, en elk ervan levert dezelfde onbehulpzame repair-prompt op wanneer je het fout doet. BuildChartExKnownXml en BuildChartExRelsXml in HotXLS hebben alle vier ingebakken, maar het is de moeite waard ze te kennen als je ooit de XML met de hand inspecteert
- Volgorde.
cx:chartDatamoet in het document vóórcx:chartkomen. Dat is het omgekeerde van de intuïtie die je van klassieke grafieken opbouwt, waar de caches in de series in de plot area leven; in een chartEx-part zit de data vooraan in een eigen blok en refereert de chart-body er alleen naar via id - Cardinaliteit.
cx:plotAreamag exact ééncx:plotAreaRegionbevatten. Niet nul, niet twee. Hier is geen combinatiegrafiek-verhaal — één region, en de series erin - Typeplaatsing. Het chart-type is helemaal geen elementnaam. Het is het
layoutId-attribuut opcx:series, met een token uit deST_SeriesLayout-enumeratie in het Microsoft-chartex-schema.XlsxChartExLayoutIdmapt de HotXLS-enum op dat token en geeft'waterfall'of'treemap'terug;XlsxChartExLayoutIdToTypedraait de mapping bij het lezen om - De relationship die klassieke grafieken nooit nodig hebben.
cx:chartDatabevat een<cx:externalData r:id="rId1"/>-element, en die relationship moet bestaan inxl/charts/_rels/chartExN.xml.rels, wijzend terug naar de worksheet die eigenaar is van de data, want Excel gebruikt hem om decx:f-bereikformules op te lossen. Laat je het rels-part weg, dan opent de grafiek leeg zonder dat iets uitlegt waarom
Hoe voeg je een waterfall- of treemap-grafiek toe in Delphi?
Op dezelfde manier als elke andere grafiek in HotXLS — de uitgebreide types zijn leden van TXLSXChartType, dus TXLSXWorksheet.AddChart accepteert ze direct en alles stroomafwaarts hangt aan de enum-waarde. Er is geen aparte chartEx-API om te leren
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Chart: TXLSXChart;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('cashflow.xlsx');
Sheet := Book.Sheets[0];
// Anchor-rechthoek in rij/kolom-coördinaten, net als een klassieke grafiek
Chart := Sheet.AddChart(xlsxChartWaterfall, 'Q3 Cash Flow',
2, 5, 20, 12);
Chart.AddSeries('Movement', 'Sheet1!$A$2:$A$9', 'Sheet1!$B$2:$B$9');
Book.SaveAs('cashflow-waterfall.xlsx');
finally
Book.Free;
end;
end;
Bij het opslaan ziet HotXLS XlsxChartIsChartEx True teruggeven, kent een part-naam toe uit een afzonderlijke chartEx-teller, en schrijft xl/charts/chartEx1.xml, zijn rels-bestand, de content-type override en het mc:AlternateContent-anchor dat XlsxChartExAnchorPayloadXml produceert. Die onafhankelijke teller doet ertoe: een workbook met twee klassieke grafieken en één waterfall krijgt chart1.xml, chart2.xml en chartEx1.xml, niet chartEx3.xml. Treemaps verschillen daarna op twee details van waterfalls, beide afgehandeld door dezelfde builder. De numerieke dimensie verandert van type — cx:numDim type="val" voor een waterfall wordt type="size" voor een treemap, want een treemap-rechthoek encodeert grootte als oppervlakte in plaats van als een uitgezette waarde — en treemaps willen meestal een categorie-hiërarchie, die het model via TXLSXChartSeries.CategoriesMultiLevel uitdrukt. Zet die flag en S.ResolveCategoryLevels leest het bereik bij het schrijven uit de worksheet, splitst het in één TXLSXChartDataCache per level achter S.CategoryLevelCount en S.CategoryLevels[], en BuildChartExLevelXml emitteert één cx:lvl per level in één enkele cx:strDim, leaf level eerst — dezelfde fysieke ordening die ECMA-376 Part 1 §21.2 voor c:multiLvlStrCache gebruikt, dus die conventie gaat over ook al doet geen van de markup dat
Chart := Sheet.AddChart(xlsxChartTreemap, 'Revenue by Region',
2, 8, 24, 16);
S := Chart.AddSeries('Revenue',
'Sheet1!$A$2:$B$40', // twee kolommen: leaf-label, dan parent-groep
'Sheet1!$C$2:$C$40');
S.CategoriesMultiLevel := True;
De cache-vorm die elke aanname van klassieke grafieken breekt
Als je een klassieke chart-cache-reader hebt geschreven, verraden drie gewoonten je op een chartEx-part, en alle drie komen uit één ontwerpbeslissing: chartex slaat in attributen en element content op wat DrawingML in kind-elementen opslaat
- Een gecachte point is
<cx:pt idx="0">42</cx:pt>. De waarde is de element content. Er is geenc:v-kind om in af te dalen, dus een parser die op zoek naarv-nodes wandelt vindt niets en meldt een lege cache ptCountenformatCodezijn attributen opcx:lvl. In een klassiekec:numCachezijn beide kind-elementen- Meerdere hiërarchie-levels zijn meerdere
cx:lvl-elementen in ééncx:strDim. Er is geen apart multi-level-cache-element zoalsc:multiLvlStrCacheapart is vanc:strCache
BuildChartExLevelXml schrijft die vorm en ParseChartExXml leest hem terug, waarbij elke waarde er via XlsxEscapeText uit gaat zodat een categorie-label met een ampersand of een angle bracket de round trip overleeft. Het is de moeite waard te noteren wat de builder doet wanneer een numeriek level geen format code draagt: hij schrijft formatCode="General" in plaats van het attribuut weg te laten, want hier is het attribuut niet optioneel zoals zijn klassieke tegenhanger
De namespace-prefix die de type-readback liet falen
De eerste versie van de HotXLS-chartEx-parser schreef prima bestanden en las ze allemaal terug als een kolomgrafiek. De root cause was één regel, en de les generaliseert naar elke OOXML-parser die je ooit zult schrijven: TXMLReader.Name geeft de naam terug zoals hij in het document staat, prefix inbegrepen. In een klassiek chart-part arriveren de meeste elementen zonder prefix, dus SameText(Reader.Name, 'series') werkt en je denkt er niet meer over na. In een chartEx-part is elk element geprefixt — de reader geeft je 'cx:series', de vergelijking faalt stilletjes op allemaal, en je krijgt geen exceptie en geen malformed XML, alleen een lege series-lijst en een chart-type dat terugvalt op de default. De fix is vergelijken op de local name, op één plek in plaats van op elke call site
// Reader.Name draagt zijn namespace-prefix: 'cx:series', niet 'series'
function IsEl(const LocalName: string): Boolean;
var
NodeName: string;
begin
NodeName := String(Reader.Name);
Result := SameText(NodeName, LocalName) or
EndsText(':' + LocalName, NodeName);
end;
ParseChartExXml gebruikt die helper voor elke elementtest en een gelijkwaardige AttrVal-helper voor attributen, want cx:autoUpdate en vrienden zijn ook geprefixt. Zodra de series-elementen daadwerkelijk worden gezien, komt het type terug van de eerste layoutId die wordt tegengekomen, en elke cx:series wordt aan zijn cx:data-blok gekoppeld door cx:dataId/@val tegen cx:data/@id te matchen. Grafieken die zijn geopend maar nooit gewijzigd nemen in plaats daarvan het oudere pad: de originele bytes replayen vanuit FRawChartXml, dus een onaangeraakt klantbestand komt eruit zoals het erin ging — dezelfde conservative-writer-discipline die sheet-level-extensies regeert in het sparkline-extensie-artikel
Wat HotXLS met deze grafieken bewust niet doet
Twee grenzen zijn het waard om ronduit te benoemen, want beide zijn keuzes en geen gaten die nog gevuld moeten worden. De ingebouwde PDF- en SVG-renderers tekenen geen waterfall- of treemap-series: de types matchen geen enkele drawing-branch, dus series-rendering wordt overgeslagen en de rest van de sheet exporteert normaal — een plausibel uitziende treemap in een geëxporteerde PDF faken zou erger zijn dan niets tekenen, want hij zou stilletjes afwijken van wat Excel voor hetzelfde bestand toont. En xlsxChartWaterfall en xlsxChartTreemap zijn de enige twee uitgebreide layouts die HotXLS vanuit het model bouwt; ST_SeriesLayout definieert ook sunburst, funnel, box-and-whisker en histogram, en een workbook dat er een bevat round-tript nog steeds via het raw-XML-replay-pad, je kunt er alleen vandaag nog geen één vanuit Delphi-code aanmaken. Het renderen van de klassieke chart-types is onveranderd en behandeld in het artikel over grafieken, afbeeldingen en drawings, en de chartEx-pipeline vaart mee in de HotXLS Delphi Excel Component voor Delphi en C++Builder