HotXLS може поставити діаграму просто на робочий аркуш, прив'язану до діапазону комірок, замість того щоб класти її на окремий аркуш діаграм. Мовою BIFF8 це означає записати фігуру малювання з OBJ-записом типу 5 і запаркувати сабстрім діаграми в кінці потоку записів аркуша — рівно та розкладка, яку продукує Excel, і рівно там, де читач очікує її знайти
Ця відмінність важлива для всіх, хто генерує операційні звіти. Аркуш діаграм — добрий дім для одного заголовного візуалу. А місячний регіональний розріз хоче діаграму поруч із числами, які вона підсумовує, на тому самому аркуші, розмірену під блок комірок, до якого вона належить, тож читач скролить один раз, а не перемикає таби й втрачає контекст
Читання вже було, запису — не було
Асиметрію варто назвати, бо вона формує роботу. HotXLS уже вмів читати вбудовані діаграми: коли потік записів аркуша містить BOF, позначений як сабстрім діаграми, парсер перемикає контекст, збирає записи діаграми, а на закриваючому EOF віддає їх назад фігурі малювання, яку представив OBJ-запис. Цей шлях був прогнаний кожною книгою, написаною Excel, яку бібліотека коли-небудь відкривала
Бракувало сторони авторства, і корисний наслідок у тому, що новий райтер мав точну специфікацію, в яку цілитися: продукувати байтову розкладку, яку наявний читач уже прив'язує назад. Немає кращого критерію приймання для можливості бінарного формату, ніж незалежно написаний читач, який ви не могли міняти
З чого складається вбудована діаграма
Три частини мусять погодитися. Шар малювання дає фігуру хост-контролю, шар об'єктів дає OBJ-запис, чиї спільні дані об'єкта декларують тип об'єкта 5, а потік записів дає сам сабстрім діаграми. Прапорці опцій на OBJ-записі — ті, що Excel пише для рамки діаграми: positioned, locked, automatic line і automatic fill, і саме це робить вбудовану діаграму за поведінкою як нативну, коли користувач клікає її
Анкер заслуговує на примітку, бо це поширване джерело багів зсуву на одиницю. API HotXLS бере номери рядків і колонок з одиниці, як і решта бібліотеки, а клієнтський анкер, записаний у файл, — з нуля. Конвертація відбувається всередині AddChartObject, тож викликаючі лишаються в тій координатній системі, яку використовують усюди інше, але кожен, хто порівнює hex-дамп із власним викликом, мусить пам'ятати, який бік тієї межі він читає
var
Book: TXLSWorkbook;
Sheet: TXLSWorksheet;
Series: array[0..1] of TXLSChartSeriesInfo;
begin
Book := TXLSWorkbook.Create(nil);
try
Book.LoadFromFile('regional-sales.xls');
Sheet := Book.Sheets[0];
FillChar(Series, SizeOf(Series), 0);
Series[0].Name := 'Actual';
Series[0].Categories := 'Data!$A$2:$A$13';
Series[0].Values := 'Data!$B$2:$B$13';
Series[0].DataLabels.ShowValue := True;
Series[0].HasDataLabels := True;
Series[1].Name := 'Target';
Series[1].Categories := 'Data!$A$2:$A$13';
Series[1].Values := 'Data!$C$2:$C$13';
Series[1].SecondaryAxis := True;
// Прив'язано до E2:M20 на цьому аркуші, рахунок з одиниці
Sheet.AddChartObject(xlsChartTypeColumn, 'Regional sales',
'Month', 'Amount', Series, 2, 5, 20, 13);
Book.SaveToFile('regional-sales-charted.xls');
finally
Book.Free;
end;
end;
FillChar на масиві серій — не прикраса. TXLSChartSeriesInfo несе кілька опційних підзаписів — мітки даних, стиль на серію, трендлінії й планки помилок, — кожен за булевим прапорцем, і частково ініціалізований запис на стеку віддасть емітеру прапорці, які ніхто не ставив. Обнуліть масив, потім ставте ті поля, які маєте на увазі
Які посилання серій приймає вбудований шлях?
Прості діапазони в стилі A1 всередині тієї самої книги, і це обмеження навмисне, а не нагляд. Кожне посилання розв'язується проти списку аркушів книги й перетворюється на індекс зовнішнього посилання, який потрібен записам діаграми. Іменований діапазон чи посилання на зовнішню книгу відкатуються на плейсхолдер із розпарсеним виразом нульової довжини, тож діаграма пишеться чисто, але та конкретна серія не має джерела даних, доки ви не наведете її на діапазон
Причина — прямий інженерний трейд. Повний шлях компіляції посилань існує на маршруті аркуша діаграм, загорнутий у шар колекції аркушів, і вийняти його чисто означало б дублювати сотню рядків логіки розв'язання заради випадку, рідкого на практиці. Вбудована діаграма майже завжди графує комірки власного аркуша чи сусіднього аркуша даних. Іменовані та зовнішні посилання покриті на шляху аркуша діаграм через AddChartSheet, тож ніщо не недоступне — просто досягається з іншої точки входу
Усе інше в моделі серій працює однаково на обох маршрутах. Прив'язка другорядної осі, стиль лінії, заливки й маркерів на серію, трендлінії, планки помилок і мітки даних — усе частина TXLSChartSeriesInfo і все емітується так само, тож визначення діаграми може переїжджати між вбудованим об'єктом і аркушем діаграм зі зміною лише виклику. Механіка груп осей за прапорцем другорядної осі розглянута в групах другорядної осі на записі BIFF
Чому заголовок діаграми прочитався як два символи?
Бо кількість символів була передана там, де очікувалася кількість байтів, а Unicode-рядки BIFF роблять цю помилку легкою для написання і важкою для побачення. Короткий Unicode-рядок BIFF починається з кількості символів і байта прапорців, і байт прапорців несе біт старшого байта, який каже, чи payload — один байт на символ, чи два. Прочитайте 16-бітний payload із кількістю символів, як ніби це байтова довжина, — і отримаєте рівно половину рядка: серія на ім'я Sales повертається як Sa, і заголовок діаграми обрізається так само, бо заголовки й мітки серій ділять шлях декодування
Що робить цей дефект помітним, так це те, що він повторився тричі в одній сім'ї записів — раз у іменах трендліній, раз у іменах pivot-діаграм і раз у заголовках діаграм. Кожен випадок виглядав як свіжий баг у новій можливості. Усі три були тим самим пропущеним множенням. Правило, яке зрештою його закрило, механічне і має застосовуватися без роздумів: щоразу, коли читаєте один із цих рядків, спершу дивіться на прапорець старшого байта й помножте кількість символів на ширину payload, перш ніж торкатися буфера. Деталі на рівні записів — у декодуванні кількостей символів XLUnicodeString і прапорця старшого байта
// Вбудована діаграма ділить шар малювання з зображеннями й фігурами,
// тож наявне малювання на аркуші зберігається. AddChartObject
// повертає індекс створеного об'єкта
var
ObjIndex: Integer;
begin
ObjIndex := Sheet.AddChartObject(xlsChartTypeLine, 'Trend',
'Week', 'Units', Series, 2, 8, 18, 16);
if ObjIndex < 0 then
raise Exception.Create('chart object was not created');
end;
Де вбудовані діаграми місце серед альтернатив
Існує три маршрути, і вони відповідають на різні питання. Вбудований об'єкт діаграми належить поруч зі своїми даними на робочому аркуші — і саме цього хоче більшість звітів. Аркуш діаграм пасує одному презентаційному візуалу і дає повний шлях компіляції посилань. Зберегти наявну діаграму з завантаженого файлу недоторканою — правильна відповідь, коли книга прийшла з Excel із форматуванням, яке ніхто не хоче, щоб бібліотека переінтерпретувала; та pass-through поведінка описана в збережених ChartML і комбінованих діаграмах
Оскільки вбудована діаграма їде шаром малювання, вона співіснує із зображеннями та фігурами на тому самому аркуші, а не замінює їх, і загальна модель того шару розглянута в діаграмах, зображеннях і малюваннях у HotXLS. Усі три маршрути постачаються в HotXLS Delphi spreadsheet component, тож вибір — про те, як звіт має виглядати, а не про те, що бібліотека може висловити
Методологічний пункт — той, що варто зберегти. Коли можливість бінарного формату має наявного читача, будуйте райтера проти читача, а не проти свого прочитання специфікації. Читач кодує роки контакту з файлами, які реальні застосунки справді продукували, включно з частинами, які специфікація формулює розлого, і райтер, який його задовольняє, значно ймовірніше задовольнить і Excel