مقاله فنی

نمودارها، تصاویر و اشکال ترسیمی HotXLS در Delphi

هر چیزی که روی شبکهٔ یک کاربرگ شناور باشد (یک نمودار، یک لوگو، یک مهر، یک جعبهٔ توضیح) یک شیء ترسیمی است، و یک شیء ترسیمی با دو چیز تعریف می‌شود: این‌که چیست، و این‌که کجا لنگر انداخته است. لنگر همان بخشی است که افراد معمولاً در آن اشتباه می‌کنند. یک نمودار در یک سلول زندگی نمی‌کند؛ بلکه در مستطیلی جای می‌گیرد که به بازه‌ای از سطرها و ستون‌ها سنجاق شده است، و داده‌ای که رسم می‌کند مجموعه‌ای جداگانه از ارجاع‌های A1 است که لنگر هیچ اطلاعی از آن ندارد. قاب را جابه‌جا کنید، نمودار سرجایش می‌ماند. سطرهایی زیر آن درج کنید، قاب هم همراه آن‌ها پایین می‌رود. حفظ درست این دو دستگاه مختصات، بیشترِ چیزی است که رفتار کد ترسیمی را درست نگه می‌دارد

HotXLS یک کتابخانهٔ نیتیو Object Pascal است که بدون اتوماسیون Excel فایل‌های XLS و XLSX را می‌خواند و می‌نویسد، و به همین دلیل دو مدل ترسیمی جداگانه دارد، چون این دو قالب فایل، اشیای ترسیمی را به شکل متفاوتی ذخیره می‌کنند. قالب BIFF8 با پسوند .xls نمودارها را در شیت‌های اختصاصی خودشان نگه می‌دارد و اشکال شناور را در یک جریان OfficeArt که به کاربرگ متصل است. قالب OOXML با پسوند .xlsx می‌تواند یک نمودار را داخل شبکه جاسازی کند، که به یک مستطیل سلولی لنگر انداخته شده، در کنار همان نوع تصاویر و اشکال شناور. مدل شیء همین شکاف را بازتاب می‌دهد، و خرابی‌هایی که ارزش نوشتن دارند همگی از اعمال قواعد یک قالب روی قالب دیگر می‌آیند

کدام ظرف چه چیزی را می‌تواند نگه دارد

انتخاب ظرف باید پیش از هر کد نموداری انجام شود، چون انواع اشیای در دسترس بین این دو قالب متفاوت است:

دیاگرام مقایسهٔ ظرف‌های ترسیم در HotXLS از Delphi؛ برگه‌های نمودار و شکل‌های OfficeArt در XLS میراث در برابر نمودارها و تصاویر و جعبه‌های متنی تعبیه‌شده در XLSX
دو قالب فایل APIهای ترسیم متفاوتی آشکار می‌کنند، پس ظرف باید پیش از نوشتن هر کد نموداری انتخاب شود
  • XLS (BIFF8): نمودارها در شیت‌های نموداری اختصاصی زندگی می‌کنند که با AddChartSheet روی مجموعهٔ Sheets ساخته می‌شوند. تصاویر، جعبه‌های متنی، مستطیل‌ها، بیضی‌ها و خط‌ها اشکال OfficeArt هستند که از طریق مجموعهٔ Shapes کاربرگ مدیریت می‌شوند. هیچ API‌ای برای جاسازی نمودار داخل شبکهٔ عادی کاربرگ وجود ندارد
  • XLSX (OOXML): نمودارها می‌توانند مستقیماً با TXLSXWorksheet.AddChart در یک کاربرگ جاسازی شوند، که به یک مستطیل سلولی لنگر انداخته شده، یا با TXLSXWorkbook.AddChartSheet روی یک شیت نموداری اختصاصی قرار گیرند. تصاویر با AddImage یا AddImageFromFile اضافه می‌شوند، و برچسب‌های شناور با AddTextBox

بنابراین نیازمندی‌ای که به شکل «یک شیت داشبورد با نمودار کنار اعداد» بیان می‌شود، در واقع نیازمندی برای .xlsx است. در .xls فقط می‌توانید با انتقال نمودار به شیت مخصوص خودش به آن نزدیک شوید، که هم نحوهٔ ناوبری کاربر در فایل را تغییر می‌دهد و هم نحوهٔ رفتار کد شما را. شیتی که سمت XLS از AddChartSheet برمی‌گرداند یک زیرجریان نموداری است، نه یک شبکه: نوشتن روی آن با 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 داده می‌شود. این یک مقدار literal است که در همان لحظهٔ فراخوانی ثبت می‌شود و هیچ ایده‌ای ندارد که ممکن است بعداً بیست سطر دیگر داده اضافه کنید. آن را از تعداد سطرهایی بسازید که بعد از نوشتن داده محاسبه کرده‌اید، هرگز پیش از آن. نمودارهای پراکندگی (Scatter) و حبابی (Bubble) همان دو آرگومان را با معانی متفاوت اُوِرلود می‌کنند: محدودهٔ categories اکنون مقدارهای X را می‌دهد و محدودهٔ values مقدارهای Y را، و شعاع حباب از یک ارجاع سوم می‌آید که از طریق BubbleSizeRange روی TXLSXChartSeries برگشتی تنظیم می‌شود. وقتی از خانوادهٔ ستونی-میله‌ای خارج می‌شوید، فراخوانی را به‌صورت «X، Y، اندازه» بخوانید، نه «categories، values»

TXLSXChartType انواع ستونی، میله‌ای، خطی، دایره‌ای، ناحیه‌ای، حلقه‌ای (doughnut)، پراکندگی، حبابی و راداری را پوشش می‌دهد، که مجموعهٔ گزارش‌گیری روزمره را پوشش می‌دهد. برای یک نمودار تمام‌صفحه بدون شبکهٔ اطراف، Book.AddChartSheet شیتی برمی‌گرداند که ویژگی IsChartSheet آن true است. این معادل .xlsx همان شیت نموداری قدیمی است و همان انتظار را دارد: محتوای سلولی روی آن ننویسید

تصاویر به‌صورت بایت وارد می‌شوند، و اندازه‌شان بر حسب EMU است

برای درج یک تصویر دو اُورلود وجود دارد، و اشتباه گرفتن آن‌ها رایج‌ترین باگ تصویری است که در بازبینی کد دیده می‌شود. AddImage(ARow, ACol, AData, AFormat) انتظار دارد بایت‌های از پیش رمزگذاری‌شدهٔ تصویر در AData باشد: محتوای خام یک PNG، JPEG، GIF یا BMP. اگر مسیر فایل را به آن بدهید، یک رشتهٔ چهل‌بایتی ذخیره کرده‌اید که هیچ نمایشگری نمی‌تواند آن را رمزگشایی کند، که دقیقاً همان گزارش آیکون تصویر خراب است که نمی‌خواهید بعد از استقرار دیباگش کنید. وقتی منبع یک فایل روی دیسک است، به‌جای آن AddImageFromFile را فراخوانی کنید و بگذارید کتابخانه بایت‌ها را بخواند و قالب را برایتان تشخیص دهد

بعد نوبت اندازه‌گیری می‌رسد. DrawingML بر حسب پیکسل اندازه نمی‌گیرد؛ بر حسب English Metric Units اندازه می‌گیرد، جایی‌که 914400 EMU برابر یک اینچ است و در 96 DPI، 9525 EMU برابر یک پیکسل. شیء TXLSXImage ویژگی‌های WidthEMU و HeightEMU را در معرض قرار می‌دهد، پس یک لوگو که قرار است 180 در 60 پیکسل رندر شود به 1714500 در 571500 EMU نیاز دارد. این تبدیل را در یک ثابت نام‌گذاری‌شده قرار دهید و بر اساس آن محاسبه کنید. اعدادِ جادویی مثل 1714500 که در سراسر کد پراکنده‌اند غیرقابل‌خواندن‌اند و اولین بار که کسی DPI هدف را تغییر دهد، بی‌سروصدا نادرست می‌شوند. سطر و ستون لنگر، به‌هرحال، 1-پایه هستند، هم‌راستا با بقیهٔ API سلولی، نه محاسبات 0-پایهٔ EMU

دیاگرام دو دستگاه مختصات پشت TXLSXWorksheet.AddChart در HotXLS؛ قاب نمودار به ردیف‌ها و ستون‌های کاربرگ لنگر می‌شود درحالی‌که دادهٔ سری‌ها از ارجاع‌های مطلق A1 استفاده می‌کند
قاب به سطرها و ستون‌ها میخ شده، در حالی که نمودار ارجاع‌های مطلق A1 را می‌خواند، و هیچ‌کدام از دو سیستم مختصات از دیگری خبر ندارد

شیت‌های نموداری و اشکال در فایل‌های XLS قدیمی

در سمت BIFF8، اُورلود غنی‌تر AddChartSheet نوع نمودار، عنوان محورها، و یک آرایهٔ باز از رکوردهای TXLSChartSeriesInfo را می‌گیرد، که هر رکورد یک نام و یک محدودهٔ categories و values به‌صورت رشته دارد. اشکال شناور موضوع جداگانه‌ای هستند: آن‌ها روی خودِ کاربرگ داده قرار می‌گیرند، از طریق مجموعهٔ Shapes آن، نه روی شیت نموداری

var
  Book: IXLSWorkbook;
  Data, Trend: IXLSWorksheet;
  Series: array[0..0] of TXLSChartSeriesInfo;
begin
  Book := TXLSWorkbook.Create;   // interface-counted: 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 نگه‌داری می‌شود و reference-counted است، پس فراخوانی Free روی آن به‌دست خودتان یک آزادسازی دوگانه راه می‌اندازد. TXLSXWorkbook از بخش‌های قبلی یک شیء ساده است و باید در یک try..finally آزاد شود. همان بازبین کدی که نبودِ Free در سمت XLSX را علامت می‌زند باید وجودِ آن را در سمت XLS هم علامت بزند، که وقتی در یک واحد با هر دو قالب کار می‌کنید یک تلهٔ واقعی است. کمک‌کننده‌های شکل خودشان یکنواخت‌اند: AddRectangle، AddOval و AddLine، به‌همراه DeleteInRange برای پاک کردن یک ناحیه از اشیای ترسیمی، همگی با جفت سطر و ستون لنگر می‌اندازند، پس قالبی که سطرهایی را بالای آن‌ها درج می‌کند، آن‌ها را هم همراه شبکه جابه‌جا می‌کند

یک ویژگی دیگر هم در فایل‌های قدیمی جای خودش را دارد. TXLSPicture.TransparentColor یک رنگ پس‌زمینهٔ انتخابی را از یک بیت‌مپ ماسک می‌کند، که همین‌طور می‌توانید یک مهر غیرمستطیلی (یک مهر «Approved»، یک واترمارک) را روی شبکه‌ای بیندازید که رندرینگ BIFF آن هرگز آلفای PNG را یاد نگرفته است. رنگی را که مهر با آن ساخته شده تنظیم کنید و مستطیل اطراف آن ناپدید می‌شود

رنگ‌های تم یک رفت‌وبرگشت BIFF8 را زنده نمی‌مانند

پرشدگی‌های ترسیمی OOXML می‌توانند به یک اسلات رنگ تم اشاره کنند، به همین دلیل رنگ‌آمیزی مجدد کل یک .xlsx با تعویض تمش کم‌هزینه است. رکوردهای ترسیمی BIFF8 چنین اسلاتی ندارند. وقتی HotXLS یک رنگ تم را روی یک شیء ترسیمی XLS اعمال می‌کند، رنگ را به یک مقدار RGB لفظی حل می‌کند و همان را ذخیره می‌کند؛ ایندکس تمی که از آن آمده به‌محض نوشته‌شدن فایل از بین می‌رود، و باز کردن دوبارهٔ فایل نمی‌تواند آن را بازیابی کند. این موضوع به‌ویژه ابزارهای گزارش‌گیری white-label را گرفتار می‌کند، از نوعی که همان سند تولیدشده را برای چندین مشتری دوباره برند می‌زنند. نگاشت تم-به-RGB را در پیکربندی خودتان نگه دارید و هر بار که تولید می‌کنید دوباره اعمالش کنید، به‌جای این‌که انتظار داشته باشید آن را از یک .xls ذخیره‌شده بازخوانی کنید

دیاگرام درون‌ریزی تصویر HotXLS از Delphi؛ AddImage بایت‌های کدگذاری‌شده می‌خواهد و AddImageFromFile فایل را می‌خواند و پیکسل‌های 96 DPI به مقادیر WidthEMU و HeightEMU بدل می‌شوند
بایت‌های تصویر و مسیرهای فایل متعلق به overloadهای متفاوت‌اند، و اندازه‌های پیکسلیِ روی صفحه پیش از رسیدن به شیء تصویر به EMU تبدیل می‌شوند

یک تصمیم مرتبط در سمت کارایی هم دیده می‌شود. می‌توان به نمای XLS گفت وقتی تمام چیزی که از یک فایل قدیمی بزرگ می‌خواهید داده‌های سلولی آن است، کل تجزیهٔ لایهٔ ترسیمی را نادیده بگیرد، با تنظیم _DisableGraphics روی true، و این زمان واقعی قابل‌توجهی را از خواندن‌های انبوه می‌کاهد. تله‌اش دائمی است: یک کتابچهٔ کاری که به این شکل باز شده هیچ جریان OfficeArt‌ای در حافظه ندارد، پس ذخیره‌کردنش اشیای ترسیمی را از وجود پاک می‌کند. این فلگ را برای کارهای تحلیلی فقط-خواندنی نگه دارید. تصویر گسترده‌تر کارایی در یادداشت‌های ما دربارهٔ کارایی کتابچه‌های کاری بزرگ در HotXLS آمده است

پایدار نگه‌داشتن لنگرها هنگام تغییر شبکه

گزارش‌ها به‌ندرت همان اندازه‌ای می‌مانند که در آن تولید شده‌اند، و اینجا همان‌جایی است که مدل لنگر از ابتدای مقاله ثمر می‌دهد. عملیات ساختاری نمای XLSX (InsertRows، DeleteRows و معادل‌های ستونی آن‌ها) لایه‌های وابسته را همراه سلول‌ها جابه‌جا می‌کنند. نواحی ادغام‌شده، هایپرلینک‌ها، کامنت‌ها، پنل‌های ثابت (frozen panes)، محدوده‌های فیلتر، فرمت‌های شرطی، اعتبارسنجی‌ها، جدول‌ها، نام‌های تعریف‌شده، و برای این موضوع، لنگرهای تصویر و نمودار همگی با هم حرکت می‌کنند. یک لوگوی لنگرشده در سطر 1 وقتی ده سطر زیر آن درج شود همچنان بالا می‌ماند. یک قاب نمودار لنگرشده زیر بلوک داده، همراه با رشد بلوک پایین می‌رود. تنها چیزی که بازنویسی نمی‌شود هر رشتهٔ محدوده‌ای است که پیش از درج به‌صورت literal ثبت کرده‌اید، چون فقط متن است و کتابخانه دلیلی برای بازنگری آن ندارد. این همان ترتیب امنی است که برای پر کردن یک قالب باید رعایت شود: ابتدا داده را بنویسید و شکل دهید، و ساخت نمودارها و قرار دادن تصاویر را به‌عنوان آخرین گذر انجام دهید، با هر رشتهٔ محدوده‌ای که از تعداد سطرهایی که بعد از درج‌ها دارید مشتق شده، نه پیش از آن

دو ابزار کوچک‌تر مجموعهٔ چیدمان را کامل می‌کنند. TXLSTextBox.SetArea در سمت XLS یک جعبهٔ متنی یا اتوشیپ موجود را روی یک مستطیل سلولی جدید دوباره لنگر می‌اندازد، که وقتی یک بلوک فوتر جابه‌جا می‌شود بهتر از حذف و بازسازی آن است. و اُورلود بیت‌مپیِ AddPicture یک TBitmap زنده با یک فلگ شفافیت اختیاری می‌گیرد، پس هرچیزی که کد VCL خودتان بتواند بکشد (یک گیج، یک نوار sparkline، نوع نموداری که فهرست بومی ارائه نمی‌دهد) می‌تواند بدون نوشتن یک فایل موقت مستقیماً روی شیت مهر شود

نمودارها و تصاویر تقریباً همیشه لایهٔ پایانیِ یک گزارشِ از قبل ساختاریافته هستند، به همین دلیل کارِ زیربنایی تعیین می‌کند که آیا آن‌ها تمیز جا می‌افتند یا نه. پر کردن داده‌ای که یک نمودار به آن ارجاع خواهد داد در تولید گزارش مبتنی بر قالب پوشش داده شده، و پایدار نگه‌داشتن شبکه زیرِ لنگرهای شما موضوع سلول‌های ادغام‌شده و کنترل چیدمان است. مستندات کامل کلاس‌ها و متدها در صفحهٔ محصول HotXLS Delphi Component موجود است