يكتب HotXLS مخططات Excel من نوع waterfall وtreemap كأجزاء cx:chartSpace مستقلة — xl/charts/chartExN.xml الذي يحمل نوع المحتوى application/vnd.ms-office.chartex+xml — ويرسيها عبر غلاف mc:AlternateContent، فتعرض Excel 2016 وما بعدها المخطط بينما يحصل القرّاء الأقدم على شكل احتياطي. اختر xlsxChartWaterfall أو xlsxChartTreemap وسيسير خط أنابيب الأجزاء بالكامل خلف ذلك
الطريقة التي يعلن بها هذا النوع من المشاكل عن نفسه عادةً هي تذكرة دعم. يرسل عميل مصنفاً بناه فريق المالية لديه في Excel 2019، تفتحه أداة التقارير لديك، تحفظه، فيختفي مخطط waterfall. لا استثناء، لا تحذير، لا شيء في السجل — المخطط ببساطة غير موجود في المخرجات. وإن ذهبت تبحث عنه في مسار كود المخططات التقليدي فستبحث طويلاً، لأن المخطط لم يكن هناك من الأساس
لماذا ليست مخططات waterfall وtreemap داخل c:chartSpace؟
لأن Microsoft لم تستطع وضعها هناك. مخطط عناصر الرسم في ECMA-376 الجزء 1 §21.2 يعدد عناصر المخطط — c:barChart وc:lineChart وc:pieChart والباقي — كاختيار مغلق، لذا فإن إضافة waterfall وtreemap وsunburst وfunnel في Excel 2016 كانت ستعني كسر مخطط يعتمد عليه كل قارئ موجود. ذهبت الأنواع الجديدة إلى مستند موازٍ بدلاً من ذلك: جزء cx:chartSpace في مساحة أسماء chartex لعام 2014، وهي http://schemas.microsoft.com/office/drawing/2014/chartex، مخزّن بجانب المخططات التقليدية لكنه غير مرتبط بها بنيوياً. الجانب الرسومي يربط العالمين معاً بـ mc:AlternateContent، آلية التوافق من ECMA-376 الجزء 3: فرع mc:Choice يصرح بـ Requires="cx1" ويحمل xdr:graphicFrame يكون فيه a:graphicData/@uri هو مساحة أسماء chartex وحمولته إشارة عارية <cx:chart r:id="rIdN"/>، بينما يحمل mc:Fallback مستطيلاً بالملاحظة التي تكتبها Excel نفسها هناك. القارئ الذي لا يفهم cx1 ملزَم بموجب عقد الجزء 3 بتبني الفرع الاحتياطي
نوع العلاقة هو المؤشر الآخر. المخطط التقليدي يتدلى من جزء الرسم بعلاقة مخطط OOXML القياسية؛ أما جزء chartEx فيستخدم http://schemas.microsoft.com/office/2014/relationships/chartEx بدلاً منها، وهو موثق في [MS-ODRAWXML]. يكشف HotXLS العالمين كثوابت — XlsxRtChartEx لتلك العلاقة، وXlsxCtChartEx لنوع المحتوى، وXlsxNsMsChartEx وXlsxNsMsChartEx2015 لمساحتي الأسماء اللتين تحتاجهما المرساة — والدالة المنطقية XlsxChartIsChartEx هي التي ترسل المخطط إلى المسار الموسع عند الحفظ. المخططات المدمجة وXML المخطط التقليدي المحفوظ تسلك المسار الآخر، وهو مشروح في مقال XML المخطط المحفوظ والمخططات المدمجة
أربع قواعد في المخطط تحدد ما إذا كان Excel سيفتح الملف
مخطط chartex أكثر تشدداً من التقليدي في أربعة مواضع محددة، وكل واحد منها ينتج نفس مطالبة الإصلاح غير المفيدة عندما تخطئ. يدمج BuildChartExKnownXml وBuildChartExRelsXml في HotXLS الأربعة كلها، لكن معرفتها تستحق العناء إن فحصت XML يدوياً يوماً ما
- الترتيب. يجب أن يأتي
cx:chartDataقبلcx:chartفي المستند. وهذا عكس الحدس الذي تبنيه من المخططات التقليدية، حيث تعيش المخازن المؤقتة داخل السلسلة داخل منطقة الرسم؛ أما في جزء chartEx فالبيانات تجلس في المقدمة داخل كتلة خاصة بها ولا يشير إليها جسم المخطط إلا بمعرّف - العددية. قد يحتوي
cx:plotAreaعلىcx:plotAreaRegionواحد بالضبط. لا صفر ولا اثنان. لا حكاية مخططات مدمجة هنا — منطقة واحدة، والسلسلة بداخلها - مكان النوع. نوع المخطط ليس اسم عنصر إطلاقاً. إنه سمة
layoutIdعلىcx:series، وتأخذ رمزاً من تعدادST_SeriesLayoutفي مخطط chartex من Microsoft. يربطXlsxChartExLayoutIdتعداد HotXLS بذلك الرمز ويعيد'waterfall'أو'treemap'؛ ويعيدXlsxChartExLayoutIdToTypeالتوجيه بالعكس عند القراءة - العلاقة التي لا تحتاجها المخططات التقليدية أبداً. يحمل
cx:chartDataعنصر<cx:externalData r:id="rId1"/>، وتلك العلاقة يجب أن توجد فيxl/charts/_rels/chartExN.xml.relsمشيرةً إلى ورقة العمل التي تملك البيانات، لأن Excel يستخدمها لحل صيغ النطاقاتcx:f. احذف جزء rels وسيفتح المخطط فارغاً بلا ما يفسر السبب
كيف تضيف مخطط waterfall أو treemap في Delphi؟
بالطريقة نفسها التي تضيف بها أي مخطط آخر في HotXLS — الأنواع الموسعة أعضاء في TXLSXChartType، لذا يستقبلها TXLSXWorksheet.AddChart مباشرةً ويعتمد كل ما بعد ذلك على قيمة التعداد. لا توجد واجهة chartEx منفصلة لتتعلمها
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Chart: TXLSXChart;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('cashflow.xlsx');
Sheet := Book.Sheets[0];
// مستطيل التثبيت بإحداثيات صف/عمود، تمامًا كالمخطط الكلاسيكي
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;
عند الحفظ، يرى HotXLS XlsxChartIsChartEx تعيد True، ويخصص اسم جزء من عداد chartEx مستقل، ويكتب xl/charts/chartEx1.xml وملف rels الخاص به وتجاوز نوع المحتوى ومرساة mc:AlternateContent التي ينتجها XlsxChartExAnchorPayloadXml. العداد المستقل مهم: مصنف فيه مخططان تقليديان ومخطط waterfall واحد يحصل على chart1.xml وchart2.xml وchartEx1.xml، لا chartEx3.xml. ثم يختلف treemap عن waterfall في تفصيلين، كلاهما يعالجه نفس الباني. البعد الرقمي يغيّر نوعه — cx:numDim type="val" في waterfall يصبح type="size" في treemap، لأن مستطيل treemap يرمّز المقدار كمساحة لا كقيمة مرسومة — وtreemap عادةً يريد تسلسلاً هرمياً للفئات، وهو ما يعبّر عنه النموذج عبر TXLSXChartSeries.CategoriesMultiLevel. فعّل تلك العلامة وسيقرأ S.ResolveCategoryLevels النطاق من ورقة العمل وقت الكتابة، ويقسمه إلى TXLSXChartDataCache واحد لكل مستوى خلف S.CategoryLevelCount وS.CategoryLevels[]، ويصدر BuildChartExLevelXml عنصر cx:lvl واحداً لكل مستوى داخل cx:strDim واحد، مستوى الورقة أولاً — الترتيب الفيزيائي نفسه الذي يستخدمه ECMA-376 الجزء 1 §21.2 مع c:multiLvlStrCache، لذا تنتقل تلك العادة حتى وإن لم ينتقل أي من الترميز
Chart := Sheet.AddChart(xlsxChartTreemap, 'Revenue by Region',
2, 8, 24, 16);
S := Chart.AddSeries('Revenue',
'Sheet1!$A$2:$B$40', // عمودان: تسمية الورقة، ثم المجموعة الأم
'Sheet1!$C$2:$C$40');
S.CategoriesMultiLevel := True;
شكل المخزن المؤقت الذي يكسر كل افتراضات المخططات التقليدية
إن كتبت قارئ مخازن مؤقتة لمخطط تقليدي من قبل، فثلاث عادات ستخونك مع جزء chartEx، والثلاث جميعها نابعة من قرار تصميمي واحد: chartex يخزن في السمات ومحتوى العناصر ما يخزنه DrawingML في عناصر فرعية
- النقطة المخزنة هي
<cx:pt idx="0">42</cx:pt>. القيمة هي محتوى العنصر. لا يوجد عنصر فرعيc:vتنزل إليه، لذا فالمحلل الذي يمشي بحثاً عن عقدvلا يجد شيئاً ويبلغ عن مخزن مؤقت فارغ ptCountوformatCodeسمتان علىcx:lvl. فيc:numCacheالتقليدي كلاهما عنصران فرعيان- مستويات التسلسل الهرمي المتعددة هي عدة عناصر
cx:lvlداخلcx:strDimواحد. لا يوجد عنصر مخزن مؤقت متعدد المستويات منفصل كما أنc:multiLvlStrCacheمنفصل عنc:strCache
يكتب BuildChartExLevelXml ذلك الشكل ويعيد ParseChartExXml قراءته، مرراً كل قيمة عبر XlsxEscapeText في طريق الخروج حتى تنجو تسمية فئة تحوي علامة عطف أو قوس زاوية من رحلة الذهاب والإياب. يجدر ملاحظة ما يفعله الباني حين لا يحمل مستوى رقمي رمز تنسيق: يكتب formatCode="General" بدلاً من إسقاط السمة، لأن السمة هنا ليست اختيارية كما هي نظيرتها التقليدية
بادئة مساحة الأسماء التي كسرت قراءة النوع
النسخة الأولى من محلل chartEx في HotXLS كتبت ملفات جيدة تماماً، وأعادت قراءة كل واحد منها كمخطط أعمدة. السبب الجذري كان سطراً واحداً، والدرس يعمم على أي محلل OOXML ستكتبه يوماً: TXMLReader.Name يعيد الاسم كما ورد في المستند، بادئته معه. في جزء مخطط تقليدي تصل معظم العناصر بلا بادئة، لذا يعمل SameText(Reader.Name, 'series') وتتوقف عن التفكير في الأمر. أما في جزء chartEx فكل عنصر مسبوق — يعطيك القارئ 'cx:series'، وتفشل المقارنة بصمت على جميعها، ولا تحصل على استثناء ولا XML تالف، فقط قائمة سلاسل فارغة ونوع مخطط يتراجع إلى الافتراضي. الإصلاح هو المقارنة على الاسم المحلي، في مكان واحد لا في كل موضع استدعاء
// Reader.Name يحمل بادئة مساحة الاسم الخاصة به: 'cx:series'، وليس '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 تلك الدالة المساعدة في كل فحص عنصر، ودالة مساعدة مكافئة AttrVal للسمات، لأن cx:autoUpdate وأصدقاءه مسبوقون أيضاً. ما إن تُرى عناصر السلسلة فعلاً حتى يعود النوع من أول layoutId يُصادف، وتُربط كل cx:series بكتلة cx:data الخاصة بها بمطابقة cx:dataId/@val مع cx:data/@id. المخططات المفتوحة دون تعديل تسلك المسار الأقدم بدلاً من ذلك: البايتات الأصلية تُعاد من FRawChartXml، لذا يخرج ملف العميل غير الملموس كما دخل — نفس انضباط الكاتب المتحفظ الذي يحكم امتدادات مستوى الورقة في مقال امتداد sparkline
ما لا يفعله HotXLS عمداً مع هذه المخططات
حدّان يستحقان أن يُذكرا بوضوح، لأن كليهما خيار لا ثغرة بانتظار سدها. عارضتا PDF وSVG المدمجتان لا ترسمان سلاسل waterfall أو treemap: الأنواع لا تطابق أي فرع رسم، لذا يُتخطى رسم السلسلة ويُصدَّر باقي الورقة بشكل طبيعي — تزييف treemap ذي مظهر مقنع في PDF مصدّر أسوأ من عدم رسم أي شيء، لأنه سيناقض بصمت ما تعرضه Excel للملف نفسه. وxlsxChartWaterfall وxlsxChartTreemap هما فقط النوعان الموسعان الوحيدان اللذان يبنيهما HotXLS من النموذج؛ كما يعرّف ST_SeriesLayout sunburst وfunnel وbox-and-whisker وhistogram، ومصنف يحوي أياً منها ما زال يمر برحلة الذهاب والإياب عبر مسار إعادة تشغيل XML الخام، فقط لا يمكنك تأليف أحدها من كود Delphi اليوم. رسم أنواع المخططات التقليدية لم يتغير ومشروح في مقال المخططات والصور والرسومات، ويأتي خط أنابيب chartEx بجانبه في HotXLS Delphi Excel Component لـ Delphi وC++Builder