HotXLS skriver Excel-waterfall- og treemap-diagrammer som selvstændige cx:chartSpace-parts — xl/charts/chartExN.xml der bærer content typen application/vnd.ms-office.chartex+xml — og forankrer dem gennem en mc:AlternateContent-indpakning, så Excel 2016 og nyere renderer diagrammet mens ældre læsere får en fallback-form. Vælg xlsxChartWaterfall eller xlsxChartTreemap, og hele part-pipelinen følger med
Sådan et problem melder sig som regel som en support-ticket. En kunde sender en workbook deres finansafdeling byggede i Excel 2019, dit rapporteringsværktøj åbner den, gemmer den, og waterfall-diagrammet er væk. Ingen exception, ingen advarsel, intet i loggen — diagrammet er bare ikke i outputtet. Går du på jagt efter det i den klassiske chart-kodesti, leder du længe, for diagrammet var der aldrig til at begynde med
Hvorfor ligger waterfall- og treemap-diagrammer ikke i c:chartSpace?
Fordi Microsoft ikke kunne lægge dem der. DrawingML-chart-schemaet i ECMA-376 Part 1 §21.2 oplister sine chart-elementer — c:barChart, c:lineChart, c:pieChart og resten — som et lukket valg, så at tilføje waterfall, treemap, sunburst og funnel i Excel 2016 ville have betydet at bryde et schema enhver eksisterende læser er afhængig af. De nye typer kom i stedet ind i et parallelt dokument: en cx:chartSpace-part i 2014-chartex-navnerummet, http://schemas.microsoft.com/office/drawing/2014/chartex, gemt ved siden af de klassiske diagrammer men strukturelt uden relation til dem. Drawing-siden syr de to verdener sammen med mc:AlternateContent, kompatibilitetsmekanismen fra ECMA-376 Part 3: mc:Choice-grenen erklærer Requires="cx1" og holder en xdr:graphicFrame hvis a:graphicData/@uri er chartex-navnerummet og hvis payload er en bar <cx:chart r:id="rIdN"/>-reference, mens mc:Fallback holder et rektangel med den besked Excel selv skriver der. En læser der ikke forstår cx1, er ifølge Part 3-kontrakten forpligtet til at tage fallback'en
Relationshiptypen er det andet afslørende tegn. Et klassisk diagram hænger på drawing-parten med den standard OOXML-chart-relationship; en chartEx-part bruger i stedet http://schemas.microsoft.com/office/2014/relationships/chartEx, dokumenteret i [MS-ODRAWXML]. HotXLS eksponerer begge verdener som konstanter — XlsxRtChartEx for den relationship, XlsxCtChartEx for content typen, XlsxNsMsChartEx og XlsxNsMsChartEx2015 for de to navnerum ankeret har brug for — og prædikatet XlsxChartIsChartEx er det der ruter et diagram ned ad den udvidede sti ved gem. Kombinationsdiagrammer og bevaret klassisk chart-XML går ned ad den anden, beskrevet i artiklen om bevaret chart-XML og kombinationsdiagrammer
Fire schema-regler der afgør om Excel åbner filen
Chartex-schemaet er strengere end det klassiske fire bestemte steder, og hvert af dem giver den samme uhjælpsomme reparation-prompt når du får det galt. BuildChartExKnownXml og BuildChartExRelsXml i HotXLS bager alle fire ind, men de er værd at kende hvis du nogensinde inspicerer XML'en i hånden
- Rækkefølge.
cx:chartDataskal komme førcx:charti dokumentet. Det er det modsatte af den intuition du bygger fra klassiske diagrammer, hvor cachene bor inde i serien inde i plot-området; i en chartEx-part sidder dataene forrest i deres egen blok, og chart-kroppen refererer kun til dem via id - Kardinalitet.
cx:plotAreamå indeholde præcis éncx:plotAreaRegion. Ikke nul, ikke to. Der er ingen kombinationsdiagram-historie her — én region, og serien inden i den - Type-placering. Chart-typen er slet ikke et element-navn. Det er
layoutId-attributten påcx:series, der tager en token fraST_SeriesLayout-enumerationen i Microsofts chartex-schema.XlsxChartExLayoutIdmapper HotXLS-enumen til den token og returnerer'waterfall'eller'treemap';XlsxChartExLayoutIdToTypekører mappingen baglæns ved læsning - Den relationship klassiske diagrammer aldrig behøver.
cx:chartDataholder et<cx:externalData r:id="rId1"/>-element, og den relationship skal findes ixl/charts/_rels/chartExN.xml.relsog pege tilbage på worksheet'en der ejer dataene, for Excel bruger den til at opløsecx:f-områdeformlerne. Udelader du rels-parten, åbner diagrammet tomt uden noget der forklarer hvorfor
Hvordan tilføjer du et waterfall- eller treemap-diagram i Delphi?
På samme måde som du tilføjer ethvert andet diagram i HotXLS — de udvidede typer er medlemmer af TXLSXChartType, så TXLSXWorksheet.AddChart tager dem direkte, og alt nedstrøms nøgler på enum-værdien. Der er ingen separat chartEx-API at lære
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Chart: TXLSXChart;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('cashflow.xlsx');
Sheet := Book.Sheets[0];
// Forankringsrektangel i række/kolonne-koordinater, samme som et klassisk diagram
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;
Ved gem ser HotXLS XlsxChartIsChartEx returnere True, allokerer et part-navn fra en separat chartEx-tæller og skriver xl/charts/chartEx1.xml, dens rels-fil, content-type-overriden og det mc:AlternateContent-anker som XlsxChartExAnchorPayloadXml producerer. Den uafhængige tæller betyder noget: en workbook med to klassiske diagrammer og ét waterfall får chart1.xml, chart2.xml og chartEx1.xml, ikke chartEx3.xml. Treemaps adskiller sig så fra waterfalls i to detaljer, begge håndteret af den samme builder. Den numeriske dimension skifter type — cx:numDim type="val" for et waterfall bliver til type="size" for et treemap, for et treemap-rektangel koder størrelse som areal frem for som en plottet værdi — og treemaps vil som regel have en kategori-hierarki, som modellen udtrykker gennem TXLSXChartSeries.CategoriesMultiLevel. Sætter du det flag, læser S.ResolveCategoryLevels området ud af worksheet'en ved skrivetidspunktet, splitter det i én TXLSXChartDataCache per niveau bag S.CategoryLevelCount og S.CategoryLevels[], og BuildChartExLevelXml emitter ét cx:lvl per niveau inde i én enkelt cx:strDim, blad-niveauet først — samme fysiske rækkefølge som ECMA-376 Part 1 §21.2 bruger for c:multiLvlStrCache, så den konvention følger med selvom intet af markup'et gør
Chart := Sheet.AddChart(xlsxChartTreemap, 'Revenue by Region',
2, 8, 24, 16);
S := Chart.AddSeries('Revenue',
'Sheet1!$A$2:$B$40', // to kolonner: blad-label, derefter overordnet gruppe
'Sheet1!$C$2:$C$40');
S.CategoriesMultiLevel := True;
Cache-formen der bryder hver antagelse fra klassiske diagrammer
Har du skrevet en klassisk chart-cache-læser, vil tre vaner forråde dig på en chartEx-part, og alle tre kommer fra én designbeslutning: chartex gemmer i attributter og element-indhold det DrawingML gemmer i child-elementer
- Et cachet punkt er
<cx:pt idx="0">42</cx:pt>. Værdien er element-indholdet. Der er ingenc:v-child at dykke ned i, så en parser der vandrer og leder efterv-noder finder intet og rapporterer en tom cache ptCountogformatCodeer attributter påcx:lvl. I en klassiskc:numCacheer begge child-elementer- Flere hierarki-niveauer er flere
cx:lvl-elementer inde i éncx:strDim. Der er ikke noget separat multi-level-cache-element på den mådec:multiLvlStrCacheer adskilt frac:strCache
BuildChartExLevelXml skriver den form, og ParseChartExXml læser den tilbage, og kører hver værdi gennem XlsxEscapeText på vej ud, så et kategori-label med en ampersand eller en vinklet parentes overlever round trip'en. Værd at bemærke hvad builder'en gør når et numerisk niveau ikke har nogen format code: den skriver formatCode="General" i stedet for at droppe attributten, for her er attributten ikke valgfri på den måde dens klassiske modpart er
Navnerums-præfikset der fik type-læsningen til at fejle
Første version af HotXLS-chartEx-parseren skrev helt fine filer og læste hver eneste af dem tilbage som et kolonnediagram. Roden var én linje, og lektionen generaliserer til enhver OOXML-parser du nogensinde kommer til at skrive: TXMLReader.Name returnerer navnet som skrevet i dokumentet, præfiks inkluderet. I en klassisk chart-part kommer de fleste elementer uden præfiks, så SameText(Reader.Name, 'series') virker, og du holder op med at tænke over det. I en chartEx-part er hvert element præfikseret — læseren giver dig 'cx:series', sammenligningen fejler tavst på alle sammen, og du får hverken exception eller misdannet XML, bare en tom serie-liste og en chart-type der falder tilbage til default. Rettelsen er at sammenligne på det lokale navn, ét sted frem for ved hvert kaldested
// Reader.Name bærer sit navnerums-præfiks: 'cx:series', ikke '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 bruger den hjælper til hver element-test og en tilsvarende AttrVal-hjælper til attributter, for cx:autoUpdate og co. er også præfikserede. Når serie-elementerne faktisk bliver set, kommer typen tilbage fra det første layoutId der mødes, og hver cx:series kobles til sin cx:data-blok ved at matche cx:dataId/@val mod cx:data/@id. Diagrammer der er åbnet men aldrig ændret, tager i stedet den ældre sti: de originale bytes afspilles fra FRawChartXml, så en urørt kundefil kommer ud som den gik ind — samme konservative-skriver-disciplin der styrer sheet-niveau-udvidelser i artiklen om sparkline-udvidelser
Hvad HotXLS bevidst ikke gør med disse diagrammer
To grænser er værd at sige klart og tydeligt, for begge er valg snarere end huller der venter på at blive fyldt. De indbyggede PDF- og SVG-renderere tegner ikke waterfall- eller treemap-serier: typerne matcher ingen tegne-gren, så serie-renderingen springes over, og resten af sheet'et eksporteres normalt — at fake et plausibelt udseende treemap i en eksporteret PDF ville være værre end at tegne intet, for det ville tavst være uenig med hvad Excel viser for samme fil. Og xlsxChartWaterfall og xlsxChartTreemap er de eneste to udvidede layouts HotXLS bygger fra modellen; ST_SeriesLayout definerer også sunburst, funnel, box-and-whisker og histogram, og en workbook der indeholder nogen af dem, round-tripper stadig gennem raw-XML-afspilningsstien — du kan bare ikke forfatte én fra Delphi-kode i dag. Renderingen af de klassiske chart-typer er uændret og dækket i artiklen om diagrammer, billeder og drawings, og chartEx-pipelinen leveres ved siden af i HotXLS Delphi Excel Component til Delphi og C++Builder