HotXLS può piazzare un grafico direttamente su un foglio di lavoro, ancorato a un intervallo di celle, invece di metterlo su un foglio grafico dedicato. In termini BIFF8 significa scrivere una shape di disegno con un record OBJ di tipo 5 e parcheggiare il substream del grafico in fondo allo stream di record del foglio, che è esattamente il layout che Excel produce ed esattamente dove il reader si aspetta di trovarlo
La distinzione conta a chiunque generi report operativi. Un foglio grafico è una casa dignitosa per una singola visualizzazione di titoli. Una consuntivazione regionale mensile vuole il grafico accanto ai numeri che riassume, sullo stesso foglio, dimensionato al blocco di celle a cui appartiene, così il lettore scorre una volta invece di cambiare tab e perdere il contesto
La lettura c'era già, la scrittura no
L'asimmetria vale la pena nominarla perché modella il lavoro. HotXLS sapeva già leggere i grafici incorporati: quando lo stream di record del foglio contiene un BOF marcato come substream grafico, il parser cambia contesto, raccoglie i record del grafico, e alla EOF di chiusura li riconsegna alla shape di disegno introdotta dal record OBJ. Quel percorso era stato esercitato da ogni workbook scritto da Excel che la libreria avesse mai aperto
Ciò che mancava era il lato della scrittura, e la conseguenza utile è che il nuovo writer aveva una specifica precisa da centrare: produrre il layout di byte che il reader esistente già riattacca. Non esiste criterio di accettazione migliore per una funzionalità di formato binario di un reader scritto indipendentemente che non hai potuto toccare
Di cosa è fatto un grafico incorporato
Tre pezzi devono accordarsi. Il layer di disegno contribuisce con una shape host-control, il layer degli oggetti con un record OBJ i cui dati comuni di oggetto dichiarano il tipo oggetto 5, e lo stream di record con il substream del grafico stesso. I flag di opzione sul record OBJ sono quelli che Excel scrive per una cornice grafico: posizionato, bloccato, linea automatica e riempimento automatico, che è ciò che fa sì che il grafico incorporato si comporti da nativo quando un utente ci clicca sopra
L'ancora merita una nota perché è una fonte comune di bug off-by-one. L'API HotXLS prende numeri di riga e colonna a base uno, in linea col resto della libreria, e la client anchor scritta nel file è a base zero. La conversione avviene dentro AddChartObject, così i chiamanti restano nel sistema di coordinate che usano dappertutto, ma chiunque confronti un hex dump con la propria chiamata deve ricordarsi da che lato di quel confine sta leggendo
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;
// Ancorato a E2:M20 su questo foglio, base uno
Sheet.AddChartObject(xlsChartTypeColumn, 'Regional sales',
'Month', 'Amount', Series, 2, 5, 20, 13);
Book.SaveToFile('regional-sales-charted.xls');
finally
Book.Free;
end;
end;
Il FillChar sull'array di serie non è decorazione. TXLSChartSeriesInfo porta diversi sub-record opzionali, etichette dati, stile per serie, trendline e barre di errore, ognuno governato da un booleano, e un record parzialmente inizializzato sullo stack consegnerebbe all'emettitore flag che nessuno ha impostato. Azzera l'array, poi imposta i campi che intendi
Quali riferimenti di serie accetta il percorso incorporato?
Intervalli in stile A1 semplici dentro lo stesso workbook, e la restrizione è deliberata e non una svista. Ogni riferimento viene risolto contro la lista dei fogli del workbook e trasformato nell'indice di riferimento esterno di cui i record del grafico hanno bisogno. Un intervallo con nome o un riferimento a workbook esterno ricade in un placeholder con espressione analizzata a lunghezza zero, così il grafico si scrive pulito ma quella particolare serie resta senza fonte dati finché non la punti a un intervallo
La ragione è un puro compromesso ingegneristico. Il percorso completo di compilazione dei riferimenti esiste sulla via del foglio grafico, incapsulato nel layer della collezione dei fogli, e tirarlo fuori pulito vorrebbe dire duplicare un centinaio di righe di logica di risoluzione per un caso raro nella pratica. Un grafico incorporato traccia quasi sempre celle del proprio foglio o di un foglio dati fratello. I riferimenti con nome ed esterni sono coperti sulla via del foglio grafico tramite AddChartSheet, così niente è indisponibile, viene solo raggiunto da un punto di ingresso diverso
Tutto il resto del modello di serie funziona in modo identico su entrambe le vie. Il binding dell'asse secondario, gli stili linea, riempimento e marker per serie, trendline, barre di errore ed etichette dati fanno tutti parte di TXLSChartSeriesInfo ed escono tutti allo stesso modo, così una definizione di grafico può muoversi tra oggetto incorporato e foglio grafico cambiando solo la chiamata. La meccanica dei gruppi di assi dietro il flag dell'asse secondario è trattata in gruppi di asse secondario sulla scrittura BIFF
Perché il titolo del grafico si leggeva come due caratteri?
Perché è stato passato un conteggio di caratteri dove ci si aspettava un conteggio di byte, e le stringhe Unicode BIFF rendono quell'errore facile da scrivere e difficile da vedere. Una stringa Unicode BIFF corta comincia con un conteggio di caratteri e un byte di flag, e il byte di flag porta il bit di high-byte che dice se il payload è di un byte per carattere o due. Leggi un payload a 16 bit col conteggio di caratteri come se fosse una lunghezza in byte e ottieni esattamente metà stringa: una serie chiamata Sales torna come Sa, e un titolo di grafico si tronca allo stesso modo perché titoli ed etichette di serie condividono il percorso di decodifica
Ciò che rende notevole questo difetto è che è ricorso tre volte nella stessa famiglia di record, una volta nei nomi delle trendline, una volta nei nomi dei pivot chart, e una volta nei titoli dei grafici. Ogni occorrenza sembrava un bug fresco in una nuova funzione. Tutte e tre erano la stessa moltiplicazione mancante. La regola che alla fine l'ha chiuso è meccanica e va applicata senza giudizio: ogni volta che leggi una di queste stringhe, consulta prima il flag di high-byte e moltiplica il conteggio di caratteri per la larghezza del payload prima di toccare il buffer. I dettagli a livello di record sono in decodifica dei conteggi di caratteri XLUnicodeString e del flag high-byte
// Il grafico incorporato condivide il layer di disegno con immagini e forme,
// quindi un disegno esistente sul foglio viene preservato. AddChartObject
// restituisce l'indice dell'oggetto creato
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;
Dove stanno i grafici incorporati rispetto alle alternative
Tre vie esistono e rispondono a domande diverse. Un oggetto grafico incorporato appartiene accanto ai suoi dati su un foglio di lavoro ed è ciò che la maggior parte dei report vuole. Un foglio grafico si addice a una singola visualizzazione di presentazione e dà il percorso completo di compilazione dei riferimenti. Preservare un grafico esistente di un file caricato, intatto, è la risposta giusta quando il workbook arriva da Excel con formattazioni che nessuno vuole far reinterpretare a una libreria; quel comportamento pass-through è descritto in ChartML preservato e grafici combinati
Dato che il grafico incorporato cavalca il layer di disegno, coesiste con immagini e forme sullo stesso foglio invece di sostituirle, e il modello generale di quel layer è trattato in grafici, immagini e disegni in HotXLS. Tutte e tre le vie sono incluse nel HotXLS Delphi spreadsheet component, così la scelta riguarda l'aspetto che il report deve avere, non ciò che la libreria può esprimere
Il punto metodologico è quello che vale la pena conservare. Quando una funzionalità di formato binario ha un reader esistente, costruisci il writer contro il reader e non contro la tua lettura della specifica. Il reader incarna anni di contatto con file che applicazioni reali hanno prodotto davvero, incluse le parti che la specifica enuncia alla lontana, e un writer che lo soddisfa ha molte più probabilità di soddisfare anche Excel