כל דבר שמרחף מעל רשת גיליון-עבודה (תרשים, לוגו, חותמת, תיבת קריאה) הוא אובייקט ציור, ואובייקט ציור מוגדר על ידי שני דברים: מה הוא, והיכן הוא מעוגן. העוגן הוא החלק שאנשים טועים בו. תרשים לא חי בתא; הוא יושב במלבן המוצמד למקטע שורות ועמודות, והנתונים שהוא מצייר הם סט נפרד של הפניות A1 שהעוגן אינו יודע עליהם דבר. הזז את המסגרת והעלילה נשארת במקומה. הכנס שורות מתחתיו והמסגרת גולשת איתן. שמירת שתי מערכות הקואורדינטות הללו מיושרות היא רוב מה שגורם לקוד ציור להתנהג
HotXLS היא ספריית Object Pascal טבעית שקוראת וכותבת XLS ו-XLSX ללא אוטומציה של Excel, והיא נושאת שני מודלי ציור נפרדים כי שני פורמטי הקובץ מאחסנים ציורים אחרת. פורמט BIFF8 .xls שומר תרשימים על גיליונות ייעודיים משלהם וצורות צפות בזרם OfficeArt המצורף לגיליון. פורמט OOXML .xlsx יכול לשבץ תרשים בתוך הרשת, מעוגן למלבן תא, לצד אותו סוג של תמונות וצורות צפות. מודל האובייקט משקף את הפיצול הזה, והכשלים ששווה לכתוב עליהם כולם נובעים מהחלת כללי פורמט אחד על השני
איזה מיכל יכול להחזיק מה
בחירת המיכל חייבת לבוא לפני כל קוד תרשים, כי סוגי האובייקטים הזמינים שונים בין השניים:
- XLS (BIFF8): תרשימים חיים על גיליונות תרשים ייעודיים שנוצרים דרך
AddChartSheetעל אוסףSheets. תמונות, תיבות טקסט, מלבנים, אליפסות וקווים הם צורות OfficeArt המנוהלות דרך אוסףShapesשל הגיליון. אין API לשיבוץ תרשים בתוך רשת גיליון רגיל - XLSX (OOXML): תרשימים יכולים להיות משובצים ישירות בגיליון עם
TXLSXWorksheet.AddChart, מעוגנים למלבן תא, או ממוקמים על גיליון תרשים ייעודי עםTXLSXWorkbook.AddChartSheet. תמונות נכנסות עםAddImageאוAddImageFromFile, ותוויות צפות עםAddTextBox
אז דרישה המנוסחת כ"גיליון לוח-בקרה עם התרשים ליד המספרים" היא באמת דרישה ל-.xlsx. אפשר לקרב אותה ב-.xls רק על ידי דחיפת התרשים לגיליון משלו, מה שמשנה כיצד המשתמש מנווט את הקובץ וכיצד הקוד שלך צריך להתנהג. הגיליון שמוחזר על ידי AddChartSheet בצד ה-XLS הוא תת-זרם תרשים, לא רשת: כתיבה אליו עם Cells.Item מייצרת זרם ציור לא-עקבי שמתחיל ללא שגיאה וש-Excel אז משליך בפתיחה. התרשים פשוט נעלם, ושום דבר בלוג הבנייה לא אומר למה. התייחס לגיליון המוחזר כתרשים-בלבד וכל מחלקת הדוחות "תרשים חסר" נעלמת
שיבוץ תרשים בגיליון XLSX
הנתיב XLSX הוא זה שיש בו מרחב תמרון, והוא המקום שבו שתי מערכות הקואורדינטות מהפתיחה הופכות קונקרטיות. מלבן העוגן שמועבר ל-AddChart מבוטא בשורות ועמודות גיליון ומקבע היכן מסגרת התרשים יושבת. נתוני הסדרה מבוטאים כהפניות A1 אבסולוטיות שכוללות את שם הגיליון. הם עצמאיים: אפשר להזיז את המסגרת לצד הרחוק של הגיליון והיא עדיין מציירת את אותם תאים
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Chart: TXLSXChart;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Sales');
Sheet.Cells[1, 1].Value := 'Region';
Sheet.Cells[1, 2].Value := 'Revenue';
Sheet.Cells[2, 1].Value := 'East';
Sheet.Cells[2, 2].Value := 1184350;
Sheet.Cells[3, 1].Value := 'Central';
Sheet.Cells[3, 2].Value := 902210;
Sheet.Cells[4, 1].Value := 'West';
Sheet.Cells[4, 2].Value := 1010675;
// מסגרת מעוגנת לשורות 6..22, עמודות 1..8
Chart := Sheet.AddChart(xlsxChartColumn, 'Revenue by Region', 6, 1, 22, 8);
Chart.AddSeries('Revenue', 'Sales!$A$2:$A$4', 'Sales!$B$2:$B$4');
Chart.ValueAxisTitle := 'USD';
Sheet.AddImageFromFile(1, 5, 'logo.png');
Book.SaveAs('dashboard.xlsx');
finally
Book.Free;
end;
end;
הארגומנט שנושך הוא מחרוזת הטווח המועברת ל-AddSeries. היא ליטרלית, נתפסת ברגע הקריאה, ואין לה מושג שאולי תצרף עוד עשרים שורות נתונים לאחר מכן. בנה אותה מספירת שורות שחישבת לאחר שהנתונים נכתבו, לעולם לא לפני. תרשימי פיזור ובועות עומסים את אותם שני ארגומנטים במשמעויות שונות: טווח הקטגוריות מספק כעת את ערכי ה-X וטווח הערכים מספק Y, ורדיוס הבועה מגיע מהפניה שלישית שנקבעת דרך BubbleSizeRange על TXLSXChartSeries המוחזר. קרא את הקריאה כ-"X, Y, size" במקום "categories, values" ברגע שאתה עוזב את משפחת העמודות-והמוטות
TXLSXChartType משתרע על עמודות, מוטות, קווים, עוגות, שטחים, דונאטים, פיזור, בועות, ותרשימי מכ"ם, מה שמכסה את רפרטואר הדיווח היומיומי. לתרשים בעמוד מלא ללא רשת מקיפה, Book.AddChartSheet מחזיר גיליון שמאפיין IsChartSheet שלו true. זהו מקביל ה-.xlsx של גיליון התרשים המורשת ונושא את אותה ציפייה: אל תכתוב תוכן תא אליו
תמונות נכנסות כבתים, והן ממודדות ב-EMU
יש שני עומסים להכנסת תמונה, וערבובן הוא באג התמונה שעולה הכי הרבה בסקירת קוד. AddImage(ARow, ACol, AData, AFormat) רוצה את בתי התמונה הכבר-מקודדים ב-AData: התוכן הגולמי של PNG, JPEG, GIF, או BMP. העבר נתיב קובץ ואחסנת מחרוזת של ארבעים בתים ששום מציג לא יכול לפענח, שזה בדיוק דוח סמל-תמונה-שבורה שלא תרצה לנפות לאחר פריסה. כאשר המקור הוא קובץ על הדיסק, קרא ל-AddImageFromFile במקום ותן לספרייה לקרוא את הבתים ולסווג את הפורמט עבורך
אחר כך מגיע המידול. DrawingML לא מודד בפיקסלים; הוא מודד ביחידות English Metric, שבהן 914400 EMU מרכיבים אינץ' וב-96 DPI, 9525 EMU מרכיבים פיקסל. אובייקט TXLSXImage חושף WidthEMU ו-HeightEMU, כך שלוגו שאמור להתרנדר 180 על 60 פיקסל דרושים 1714500 על 571500 EMU. שים את ההמרה הזו בקבוע בעל שם וחשב כנגדה. מספרי קסם כמו 1714500 פזורים בקוד אינם קריאים ושקט-שגויים בפעם הראשונה שמישהו משנה את DPI היעד. שורת ועמודת העוגן, אגב, הם 1-בסיסיים, תואמים את שאר ממשק התאים ולא את חשבון ה-EMU ה-0-בסיסי
גיליונות תרשים וצורות בקובצי XLS מורשת
בצד ה-BIFF8 העומס העשיר יותר של AddChartSheet לוקח את סוג התרשים, את כותרות הצירים, ו-open array של רשומות TXLSChartSeriesInfo, שבה כל רשומה מחזיקה שם וטווח קטגוריות וערכים כמחרוזות. צורות צפות הן עניין נפרד: הן הולכות על גיליון הנתונים עצמו, דרך אוסף Shapes שלו, לא על גיליון התרשים
var
Book: IXLSWorkbook;
Data, Trend: IXLSWorksheet;
Series: array[0..0] of TXLSChartSeriesInfo;
begin
Book := TXLSWorkbook.Create; // נספר-הפניות כממשק: אין לקרוא ל-Free
Data := Book.Sheets.Add;
Data.Name := 'Data';
Data.Cells.Item[1, 1].Value := 'Month';
Data.Cells.Item[1, 2].Value := 'Units';
Data.Cells.Item[2, 1].Value := 'Apr';
Data.Cells.Item[2, 2].Value := 1530;
Data.Cells.Item[3, 1].Value := 'May';
Data.Cells.Item[3, 2].Value := 1721;
Series[0].Name := 'Units';
Series[0].Categories := 'Data!$A$2:$A$3';
Series[0].Values := 'Data!$B$2:$B$3';
Trend := Book.Sheets.AddChartSheet('Trend', xlsChartTypeLine,
'Units sold', 'Month', 'Units', Series);
// Trend הוא תת-זרם של תרשים: לעולם אין לקרוא לו למתודות של תא
Data.Shapes.AddTextBox('Source: ERP nightly export', 6, 1, 8, 4);
Data.Shapes.AddPicture('approved-stamp.bmp');
Book.SaveAs('trend.xls');
end;
שני פרטי אורך-חיים חשובים כאן, והם מושכים לכיוונים מנוגדים. TXLSWorkbook מוחזק דרך ממשק IXLSWorkbook והוא בעל ספירת-הפניות, כך שקריאה ל-Free עליו בעצמך מפעילה שחרור כפול. TXLSXWorkbook מהסעיפים הקודמים הוא אובייקט רגיל וחייב להיות משוחרר ב-try..finally. אותו סוקר קוד שמסמן Free חסר בצד ה-XLSX צריך לסמן אחד נוכח בצד ה-XLS, מה שהוא מכשול אמיתי כשעובדים בשני הפורמטים באותה יחידה. עוזרי הצורה עצמם אחידים: AddRectangle, AddOval, ו-AddLine, עם DeleteInRange לניקוי אזור מציורים, כולם מעוגנים בזוגות שורה ועמודה, כך שתבנית שמכניסה שורות מעליהן מזיזה אותן יחד עם הרשת
מאפיין נוסף משתלם על קובצי מורשת. TXLSPicture.TransparentColor ממסך צבע רקע נבחר מחוץ ל-bitmap, שזה כיצד מטילים חותמת לא-מלבנית (חותמת "Approved", סימן מים) מעל הרשת בפורמט שעיבוד ה-BIFF שלו מעולם לא למד alpha של PNG. קבע את הצבע שכנגדו החותמת נוצרה והמלבן המקיף נעלם
צבעי ערכת-נושא לא שורדים round-trip של BIFF8
מילויי ציור של OOXML יכולים להצביע על חריץ צבע ערכת-נושא, מה שהופך צביעה-מחדש של .xlsx שלם לזול על ידי החלפת ערכת הנושא שלו. לרשומות ציור BIFF8 אין חריץ כזה. כאשר HotXLS מחיל צבע ערכת-נושא על ציור XLS הוא פותר את הצבע לערך RGB ליטרלי ומאחסן אותו; אינדקס ערכת-הנושא שממנו הגיע נעלם ברגע שהקובץ נכתב, ופתיחה מחדש לא יכולה לשחזרו. זה תופס במיוחד כלי דיווח white-label, מהסוג שממתג-מחדש את אותו מסמך מחולל עבור לקוחות רבים. שמור את מיפוי ערכת-הנושא-ל-RGB בתצורה שלך והחל מחדש בכל פעם שאתה מחולל, במקום לצפות לקרוא אותו בחזרה מתוך .xls שמור
החלטה קשורה עולה בצד הביצועים. אפשר לומר ל-facade של XLS לדלג על פענוח שכבת הציור לחלוטין כאשר כל מה שרוצים מקובץ מורשת גדול הוא נתוני התא שלו, על ידי הגדרת _DisableGraphics ל-true, וזה מוריד זמן אמיתי מקריאות כמותיות. התפיסה קבועה: לחוברת עבודה שנפתחת כך אין זרם OfficeArt בזיכרון, כך ששמירתה כותבת את הציורים מחוץ לקיום. שמור את הדגל לעבודות אנליטיקה לקריאה-בלבד. תמונת הביצועים הרחבה יותר נמצאת ב-הערותינו על ביצועי חוברת-עבודה גדולה ב-HotXLS
שמירת עוגנים יציבים בזמן שהרשת משתנה
דוחות רק לעיתים רחוקות נשארים בגודל שבו נוצרו, וכאן מודל העוגן מהפתיחה משתלם. הפעולות המבניות של facade ה-XLSX (InsertRows, DeleteRows, ומקבילות העמודות) מזיזות את השכבות התלויות יחד עם התאים. אזורים ממוזגים, היפר-קישורים, הערות, חלוניות מוקפאות, טווחי סינון, עיצובים מותנים, אימותים, טבלאות, שמות מוגדרים, ו, לנושא זה, עוגני תמונה ותרשים כולם נוסעים יחד. לוגו המעוגן בשורה 1 נשאר בראש כשעשר שורות נכנסות מתחתיו. מסגרת תרשים המעוגנת מתחת לבלוק הנתונים גולשת למטה ככל שהבלוק גדל. הדבר היחיד שלא נכתב מחדש הוא כל מחרוזת טווח שתפסת כליטרל לפני שההכנסה קרתה, שכן היא רק טקסט שאין לספרייה סיבה לחזור אליו. זה מקבע את הסדר הבטוח למילוי תבנית: כתוב ועצב מחדש את הנתונים תחילה, וצור תרשימים והצב תמונות כמעבר הסופי, עם כל מחרוזת טווח נגזרת מספירות השורות שיש לך לאחר ההכנסות, לא לפני
שני כלים קטנים יותר משלימים את ערכת המיקום. TXLSTextBox.SetArea בצד ה-XLS מעגן מחדש תיבת טקסט קיימת או צורה אוטומטית על מלבן תא חדש, מה שעדיף על מחיקה ויצירה מחדש כשבלוק כותרת-תחתונה זז. ועומס ה-bitmap של AddPicture לוקח TBitmap חי עם דגל שקיפות אופציונלי, כך שכל דבר שקוד ה-VCL שלך יכול לצייר (מד, רצועת sparkline, סוג תרשים שהרשימה הטבעית לא מציעה) יכול להיות מוטבע ישירות לגיליון בלי לכתוב קובץ זמני תחילה
תרשימים ותמונות הם כמעט תמיד השכבה המסיימת על דוח שכבר מובנה, ולכן היסודות מחליטים אם הם נוחתים בנקיון. מילוי הנתונים שתרשים יפנה אליהם מכוסה ב-יצירת דוחות מונחית-תבנית, ושמירת הרשת יציבה מתחת לעוגנים שלך היא הנושא של תאים ממוזגים ובקרת פריסה. תיעוד מחלקות ומתודות מלא חי בעמוד המוצר של HotXLS Delphi Component