מאמר טכני

קבוצות ציר משני בתרשימי BIFF8 ב-Delphi עם HotXLS

HotXLS כותבת קבוצות ציר משני בתרשימי BIFF8 באמצעות פליטת בלוק AxisParent שני, ולא באמצעות הוספת קבוצת תרשים שנייה אחרי הצירים. בתת-הזרם של תרשים XLS קלאסי כל קבוצת תרשים — ChartFormat, רשומת סוג התרשים ו-CrtLink — חיה בתוך בלוק קבוצת הצירים שלה, וכל סדרה נקשרת לאחת מהן דרך SerToCrt. הופכים את הקינון הזה ואין קבוצת תרשים שנייה שסדרה יכולה להיקשר אליה, בלי קשר לכמות רשומות הציר שתפליטו

מדוע קבוצת תרשים שנייה אחרי הצירים אינה נקשרת לשום דבר

הדקדוק הוא כל התשובה, והוא מופיע בשורה אחת של ABNF. כלל CHARTFOMATS ב-[MS-XLS] 2.1.7.20.1 אומר AxesUsed 1*2AXISPARENT, ואז מפרט AXISPARENT = AxisParent Begin Pos [AXES] 1*4CRT End. קוראים את שתי ההפקות יחד והצורה נובעת מעצמה: קבוצות התרשים הן ילדים של קבוצת צירים ולא siblings שלה. תת-זרם תרשים עם שני זוגות צירים וקבוצת תרשים נגררת אינו תרשים דו-צירי עם quirks של layout; זהו תרשים עם קבוצת תרשים אחת וקבוצת רשומות ציר יתומות. זה חשוב מפני ש-SerToCrt ($1045), שיושבת בתוך בלוק SERIESFORMAT, נושאת אינדקס קבוצת תרשים שמבוסס על 0 ולא אינדקס ציר. כתיבת crt = 1 כשקיים רק בלוק CRT אחד מפנה סדרה לקבוצת תרשים שמעולם לא נפלטה. האינטואיציה שמכשילה אנשים נובעת משם הרשומה: AXESUSED ($1046) נשמע כאילו הוא סופר צירים, ולכן הצעד הטבעי הוא לפלוט עוד צירים. הוא סופר קבוצות צירים, וכל קבוצת צירים גוררת יחד אזור plotting שלם וקבוצת תרשים

סימון סדרה עבור קבוצת הציר המשני

בצד HotXLS הדבר מתכנס ל-Boolean אחד. TXLSChartSeriesInfo נושאת שדה SecondaryAxis, והגדרתו על סדרה כלשהי במערך שאתם מוסרים ל-TXLSWorksheets.AddChartSheet מעבירה את כל המחולל למצב של שתי קבוצות. אין קריאה נפרדת של "הפעל ציר משני" ואין פרמטר של מספר צירים, מפני שהמספר ניתן לגזירה: אם סדרה כלשהי רוצה את הקבוצה המשנית, התרשים זקוק לשתיים

var
  Wb: TXLSWorkbook;
  Series: array [0..1] of TXLSChartSeriesInfo;
begin
  Wb := TXLSWorkbook.Create;
  try
    Wb.Sheets.Add.Name := 'Data';
    // ... מלא את A1:C12 בקטגוריות, revenue ו-margin ...

    Series[0] := Default(TXLSChartSeriesInfo);   // לעולם אל תעשה FillChar לרשומה הזו
    Series[0].Name := 'Revenue';
    Series[0].Categories := 'Data!$A$1:$A$12';
    Series[0].Values := 'Data!$B$1:$B$12';

    Series[1] := Default(TXLSChartSeriesInfo);
    Series[1].Name := 'Margin';
    Series[1].Categories := 'Data!$A$1:$A$12';
    Series[1].Values := 'Data!$C$1:$C$12';
    Series[1].SecondaryAxis := True;             // AXESUSED הופך ל-2

    Wb.Sheets.AddChartSheet('Dual Axis', xlsChartTypeLine,
      'Revenue vs Margin', '', '', Series);
    Wb.SaveAs('dual-axis.xls', xlExcel97);
  finally
    Wb.Free;
  end;
end;

השורה Default(TXLSChartSeriesInfo) אינה קישוט. TXLSChartSeriesInfo מערבבת שדות מנוהלים (שמות WideString, מערכי trendline ו-error-bar דינמיים) עם איברי Boolean פשוטים, ו-Delphi מבטיחה שהשדות המנוהלים ינוקו עבורכם בלבד. השאירו את SecondaryAxis ללא אתחול והוא יהיה מה שהיה ב-stack, מה שבפועל אומר שאותו binary מייצר תרשים עם ציר יחיד כשהוא מורץ מ-host של console ותרשים עם שני צירים תחת test runner. טווחי הקטגוריות והערכים, בינתיים, נפתרים דרך טבלת EXTERNSHEET של חוברת העבודה לפני שהמחולל רואה אותם — אותו מנגנון אינדקסים שמכוסה באופן שבו HotXLS מסווגת קישורים חיצוניים של BIFF SupBook ו-XTI — ולכן טווח שקורא ל-sheet לא מוכר מידרדר ל-placeholder ריק BRAI במקום להכשיל את הבנייה

מה HotXLS פולטת כשסדרה היא משנית

ה-emitter משנה צורה ולא רק ערך. ללא סדרה משנית, AXESUSED מכילה 1 (או 0 עבור pie ו-3D pie, שאין בהם קבוצות צירים כלל) ואחריה מגיע בלוק AxisParent אחד. עם אחת, AXESUSED מכילה 2 והמחולל מריץ את הבלוק פעמיים, כאשר iax — המילה הראשונה ב-payload בן 18 הבתים של AxisParent ($1041) — מוגדרת תחילה ל-0 ואז ל-1. כל מעבר פולט Pos, את ציר הקטגוריות ואת ציר הערכים (Axis, $101D), את סמן PlotArea ($1035), Frame ברירת מחדל, אחר כך ChartFormat ($1014), את רשומת סוג התרשים, CrtLink ($1022) ושני סמני End שסוגרים את קבוצת התרשים ואת קבוצת הצירים. הסדרה המשנית נקשרת אז ב-SerToCrt crt = 1, והראשית שומרת crt = 0. רשומה אחת בכוונה אינה משוכפלת: ה-legend נפלט רק בקבוצה הראשונה, מפני ש-Excel נותנת לתרשים legend אחד בלי קשר למספר קבוצות הצירים שלו. עוד שתי תכונות כדאי לציין במפורש. שינוי מבנה ה-emitter כך שקבוצת התרשים תהיה בתוך בלוק ה-axis-parent לא שינה את הפלט של תרשימים רגילים — ללא סדרה משנית תת-הזרם זהה בייט-לבייט לגרסה הקודמת, מפני שפרמטריזציה של AddAxisParent עם iax = 0 היא בדיוק נתיב הקוד הישן. והמחולל עדיין פולט זוג צירים מלא לכל קבוצה, לכן קבוצה משנית מגיעה תמיד עם ציר קטגוריות משלה גם כאשר אכפת לכם רק מסקאלת הערכים

כיצד inspection של תרשים משחזרת את הקישור לקבוצת הצירים

הקריאה היא שני מעברים על רשימת הרשומות, והיא חייבת להיות כזו מפני ש-AXESUSED מגיעה לפני הבלוקים שהיא מתארת. המעבר הראשון מחפש רק את $1046 וקורא את המילה הראשונה שלו כמספר קבוצות הצירים. הערך הזה מתחיל ב-1 ומועלה אך לעולם אינו מונמך: HotXLS לוקחת את המקסימום בין הספירה הנוכחית למוצהרת, כך ש-AXESUSED פגומה או כפולה אינה יכולה להחזיר לאחור תרשים שכבר נראה שמצהיר על שתי קבוצות. המעבר השני עוקב אחר קבוצת הצירים הנוכחית, מעדכן אותה בכל AxisParent ומחתים את האינדקס הזה על כל רשומת Axis שהוא פוגש עד שמופיע AxisParent הבא

var
  Model: TXLSChartModel;
  i: Integer;
begin
  // Sheets[1] הוא worksheet הנתונים, Sheets[2] הוא גיליון התרשים
  Model := Wb.Sheets[2]._Chart.GetChartModel;
  try
    if Model.AxisGroupCount = 2 then
      Writeln('AXESUSED declares a secondary axis group');
    for i := 0 to Model.AxisCount - 1 do
      Writeln('axis ', i, ' group ', Model.GetAxis(i).AxisGroup);
    for i := 0 to Model.SeriesCount - 1 do
      Writeln('series ', i, ' chart group ', Model.GetSeries(i).ChartGroup);
  finally
    Model.Free;
  end;
end;

שתי מגבלות ראוי לקרוא להן בשמן. TXLSChartModel.AxisGroupCount מדווחת על מה שהקובץ מצהיר, לא על כמה בלוקים של AxisParent נמצאו בפועל; קובץ שאומר 2 ושולח בלוק אחד ידווח 2, ו-AxisCount הוא המקום שבו מבחינים. ובצד הצירים, TXLSChartAxis.AxisGroup הוא חותמת positional: הוא מתעד בתוך איזה בלוק נקרא הציר, וזה הדבר היחיד שהפורמט מספר לכם. בצד הסדרות, פענוח SerToCrt מותנה בכך שנמצאים בתוך בלוק Series, מפני שאותו record id מופיע בהקשרים שבהם הוא אינו binding של סדרה, ומפענח ללא guard ישמח לדרוס את הסדרה הלא נכונה

אימות צירים משניים בלי קובץ Excel אמיתי

האימות כאן לא נזקק לקובץ Excel עם ציר משני, וזה החלק השימושי בסיפור. פענוח מבני הוא תכונה של רצף הרשומות, ולכן רצף מסונתז מוכיח אותה בדיוק כמו רצף שנלכד. הרגרסיה בונה AXESUSED עם payload של 2, אחר כך שני בלוקים של AxisParent שכל אחד עוטף ציר קטגוריות וציר ערכים, וטוענת שהמודל חוזר עם AxisGroupCount = 2, ארבעה צירים שמוחתמים 0, 0, 1, 1 וסוגי הצירים הצפויים בזוג השני

// אימות מבני ללא מעורבות של קובץ Excel כלל
Chart := TXLSCustomChart.Create(nil, $0600);
try
  AddWordRecord($1046, [2]);   // AXESUSED: שתי קבוצות צירים
  AddAxisParentGroup(0);       // AxisParent iax=0 + Begin + 2 Axis + End
  AddAxisParentGroup(1);       // AxisParent iax=1 + Begin + 2 Axis + End

  Model := Chart.GetChartModel;
  Assert.AreEqual(2, Model.AxisGroupCount);
  Assert.AreEqual(4, Model.AxisCount);
  Assert.AreEqual(0, Model.GetAxis(1).AxisGroup);
  Assert.AreEqual(1, Model.GetAxis(2).AxisGroup);
finally
  Model.Free;
  Chart.Free;
end;

שתי הערות מעשיות אם אתם מסנתזים רשומות בעצמכם. TXLSCustomChart.AddData(RecID, Len, nil) עושה dereference ל-payload כאשר Len אינו אפס, לכן סמני Begin ($1033) ו-End ($1034) חייבים להתווסף באורך אפס ולא עם blob nil ואורך ישן. ורצף סינתטי מוכיח את ה-decoder ולעולם לא את קבלת הפלט שלכם על ידי Excel — צד הכתיבה נבנה מה-ABNF ואז נבדק ב-round-trip דרך GetChartModel, תוך assert על חותמות לכל ציר 0/0/1/1 ועל קבוצות התרשים של הסדרות 0/1, עם הנתיב הזהה בייט-לבייט ללא משני כ-safety net. זו אותה עמדה שמרנית שעליה נבנית שאר תמיכת התרשימים, התמונות והציורים של HotXLS עבור Delphi: לפענח את מה שהרשומות אומרות ולסרב לנחש layout בינארי שלא קראתם במפרט. חבילת Delphi המלאה הריצה 1650 מתוך 1650 ב-Win32 וב-Win64 אחרי שצד הכתיבה נחת

פרמטרים של סצנת Chart3d ומלכודת fAuto

שני פרטים סמוכים מכשילים אנשים שעוברים מעבר לתרשים ברירת המחדל. הראשון הוא Chart3d ($103A, [MS-XLS] 2.4.46), payload שטוח בן 14 בתים שנפלט בתוך קבוצת התרשים עבור וריאנטים תלת-ממדיים: anRot (סיבוב, 0 עד 360), anElev (גובה, חתום, -90 עד 90), pcDist (מרחק פרספקטיבה, 0 עד 100, מתעלמים ממנו אלא אם fPerspective מוגדר), pcHeight ו-pcDepth (אחוז מרוחב התרשים, 5 עד 500), pcGap (0 עד 500) ו-grbit שהביטים שלו הם fPerspective $0001, fCluster $0002, fAutoscale $0004, f3DScaling $0010 ו-f2DWalls $0020. המפרט מוסיף אילוצים ש-layout הרשומה לא יאכוף עבורכם: בתרשים bar שעבר transpose, anRot ו-anElev אינם יכולים לעלות על 44, ובתרשים pie anElev אינו יכול להיות שלילי

השני הוא ביט fAuto, והוא זה שמייצר את הדיווח "התעלמו מהצבעים שלי". LineFormat ($1007), AreaFormat ($100A) ו-MarkerFormat ($1009) נושאות כולן fAuto בביט 0 של grbit, וכאשר הביט מוגדר Excel מחילה את הסגנון האוטומטי שלה ומתייחסת לערכי RGB המפורשים, לסגנון הקו, לצורת הסמן ולגודל הסמן שלצדו כקישוט. כל emitter שכותב סגנון מותאם לסדרה חייב לנקות את ביט 0; emitters ברירת המחדל משאירים אותו מוגדר בדיוק כדי ש-Excel תבחר את הפלטה. אם אתם עורכים חוברת עבודה קיימת במקום לבנות אחת, כללי השימור שונים שוב ומכוסים בעריכת תרשימי Excel בלי לאבד ChartML שנשמר

קבוצות ציר משני, binding של SerToCrt ומודל התרשים המטופס שמוצג כאן נשלחים ברכיב HotXLS Delphi spreadsheet component עבור Delphi ו-C++Builder, שקורא וכותב תרשימי BIFF8 ללא Excel מותקן; דף המוצר נושא את ההפניה המלאה לרשומות התרשים ואת רשימת ה-overloads של AddChartSheet