Artikel Teknis

Secondary Axis Group Chart BIFF8 di Delphi dengan HotXLS

HotXLS menulis BIFF8 chart secondary axis group dengan menerbitkan block AxisParent kedua, bukan menambahkan chart group kedua setelah axes. Dalam Classic XLS chart substream, setiap chart group — ChartFormat, chart type record, CrtLink — berada di dalam axis-group block miliknya sendiri, dan setiap series mengikat ke salah satunya melalui SerToCrt. Balik nesting tersebut dan tidak ada chart group kedua yang dapat menjadi tujuan binding series, sebanyak apa pun axis record yang Anda terbitkan

Mengapa chart group kedua setelah axes tidak mengikat apa pun?

Grammar adalah seluruh jawabannya, dan jawabannya hanya satu baris ABNF. Rule CHARTFOMATS dalam [MS-XLS] 2.1.7.20.1 menyatakan AxesUsed 1*2AXISPARENT, lalu menjabarkan AXISPARENT = AxisParent Begin Pos [AXES] 1*4CRT End. Baca kedua production tersebut bersama-sama dan bentuknya jelas: chart group adalah child dari axis group, bukan sibling-nya. Chart substream dengan dua pasangan axis dan satu chart group di bagian akhir bukan dual-axis chart dengan layout yang aneh; itu chart dengan satu chart group dan sekumpulan axis record yatim. Ini penting karena SerToCrt ($1045), yang berada di dalam block SERIESFORMAT, membawa index chart group berbasis nol, bukan index axis. Menulis crt = 1 ketika hanya ada satu block CRT menunjuk series ke chart group yang tidak pernah diterbitkan. Intuisi yang menjerumuskan orang berasal dari nama record: AXESUSED ($1046) terdengar seperti menghitung axis, sehingga langkah berikutnya yang alami adalah menerbitkan lebih banyak axis. Padahal ia menghitung axis group, dan setiap axis group membawa plot area serta chart group lengkap

Menandai series untuk secondary axis group

Di sisi HotXLS, semuanya menyusut menjadi satu Boolean. TXLSChartSeriesInfo memiliki field SecondaryAxis, dan mengaturnya pada series mana pun di array yang Anda berikan kepada TXLSWorksheets.AddChartSheet mengubah seluruh builder menjadi two-group mode. Tidak ada pemanggilan terpisah untuk "enable secondary axis" dan tidak ada parameter axis-count, karena count dapat diturunkan: jika ada series yang menginginkan secondary group, chart membutuhkan dua group

var
  Wb: TXLSWorkbook;
  Series: array [0..1] of TXLSChartSeriesInfo;
begin
  Wb := TXLSWorkbook.Create;
  try
    Wb.Sheets.Add.Name := 'Data';
    // ... isi A1:C12 dengan category, revenue, dan margin ...

    Series[0] := Default(TXLSChartSeriesInfo);   // jangan pernah FillChar record ini
    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 menjadi 2

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

Baris Default(TXLSChartSeriesInfo) bukan hiasan. TXLSChartSeriesInfo mencampur managed field (nama WideString, dynamic trendline, dan error-bar array) dengan member Boolean biasa, dan Delphi hanya menjamin managed field dibersihkan untuk Anda. Biarkan SecondaryAxis tidak diinisialisasi dan nilainya akan menjadi apa pun yang ada di stack, yang dalam praktik berarti binary yang sama menghasilkan single-axis chart ketika dijalankan dari console host dan dual-axis chart di bawah test runner. Sementara itu, category dan value range di-resolve melalui EXTERNSHEET table workbook sebelum builder melihatnya — machinery index yang sama yang dibahas dalam cara HotXLS mengklasifikasikan SupBook dan XTI external link BIFF — sehingga range yang menyebut sheet tidak dikenal turun menjadi empty placeholder BRAI, bukan menggagalkan build

Apa yang diterbitkan HotXLS ketika sebuah series bersifat secondary?

Emitter mengubah bentuk, bukan hanya value. Tanpa secondary series, AXESUSED berisi 1 (atau 0 untuk pie dan 3D pie, yang sama sekali tidak memiliki axis group) dan satu block AxisParent mengikuti. Dengan satu secondary series, AXESUSED berisi 2 dan builder menjalankan block dua kali, dengan iax — word pertama dari payload AxisParent ($1041) berukuran 18 byte — diset ke 0 lalu 1. Setiap pass menerbitkan Pos, category axis dan value axis (Axis, $101D), marker PlotArea ($1035), default Frame, lalu ChartFormat ($1014), chart type record, CrtLink ($1022), dan dua marker End untuk menutup chart group dan axis group. Secondary series kemudian diikat dengan SerToCrt crt = 1, sedangkan primary tetap crt = 0. Satu record sengaja tidak diduplikasi: legend hanya diterbitkan di group pertama karena Excel memberi sebuah chart satu legend, berapa pun axis group yang dibawanya. Dua property lain juga perlu dijelaskan. Menata ulang emitter agar chart group berada di dalam axis-parent block tidak mengubah output untuk chart biasa — tanpa secondary series, substream byte-identical dengan versi sebelumnya karena parameterisasi AddAxisParent dengan iax = 0 tepat merupakan jalur code lama. Builder juga tetap menerbitkan pasangan axis lengkap per group, sehingga secondary group selalu datang bersama category axis sendiri walaupun Anda hanya membutuhkan value scale-nya

Bagaimana chart inspection memulihkan binding axis-group?

Read dilakukan dalam dua pass atas record list, dan memang harus begitu karena AXESUSED datang sebelum block yang dideskripsikannya. Pass pertama hanya mencari $1046 dan membaca word pertamanya sebagai axis-group count. Nilai tersebut mulai dari 1 dan hanya dinaikkan, tidak pernah diturunkan: HotXLS mengambil maximum antara count saat ini dan declared count, sehingga AXESUSED yang malformed atau duplicate tidak dapat menurunkan chart yang sudah diketahui mendeklarasikan dua group. Pass kedua melacak current axis group, memperbaruinya pada setiap AxisParent, lalu menempelkan index tersebut pada setiap record Axis yang ditemui sampai AxisParent berikutnya muncul

var
  Model: TXLSChartModel;
  i: Integer;
begin
  // Sheets[1] adalah data worksheet, Sheets[2] adalah chart sheet
  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;

Dua batasan layak disebut. TXLSChartModel.AxisGroupCount melaporkan apa yang dideklarasikan file, bukan berapa block AxisParent yang benar-benar ditemukan; file yang mengatakan 2 tetapi hanya mengirim satu block akan melaporkan 2, dan AxisCount-lah tempat Anda menyadarinya. Selain itu, TXLSChartAxis.AxisGroup adalah positional stamp: ia mencatat block tempat axis dibaca, yang merupakan satu-satunya hal yang diberitahukan format. Di sisi series, decoding SerToCrt dibatasi hanya ketika berada di dalam Series block, karena record id yang sama muncul dalam context yang bukan series binding, dan decoder tanpa guard akan dengan senang hati menimpa series yang salah

Memverifikasi secondary axis tanpa file Excel sungguhan

Verifikasi di sini tidak membutuhkan file Excel dengan secondary axis, dan itulah bagian yang berguna. Structural decoding adalah property dari urutan record, sehingga sequence yang disintesis membuktikannya sama tepatnya dengan sequence hasil capture. Regression membangun AXESUSED dengan payload 2, lalu dua block AxisParent yang masing-masing membungkus category axis dan value axis, kemudian melakukan assertion bahwa model kembali dengan AxisGroupCount = 2, empat axis yang diberi stamp 0, 0, 1, 1, serta axis type yang diharapkan pada pasangan kedua

// Verifikasi struktural tanpa melibatkan file Excel sama sekali
Chart := TXLSCustomChart.Create(nil, $0600);
try
  AddWordRecord($1046, [2]);   // AXESUSED: dua axis group
  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;

Dua catatan praktis jika Anda mensintesis record sendiri. TXLSCustomChart.AddData(RecID, Len, nil) melakukan dereference payload ketika Len non-zero, sehingga marker Begin ($1033) dan End ($1034) harus ditambahkan dengan length nol, bukan nil blob dan stale length. Dan sequence sintetis membuktikan decoder, bukan penerimaan output Anda oleh Excel — write side disusun dari ABNF lalu diperiksa dengan round-trip melalui GetChartModel, assertion stamp per axis 0/0/1/1 dan chart group per series 0/1, dengan jalur tanpa secondary yang byte-identical sebagai safety net. Itulah sikap konservatif yang sama yang menjadi dasar dukungan chart, image, dan drawing HotXLS untuk Delphi: decode apa yang dikatakan record dan tolak menebak layout binary yang belum Anda baca di spesifikasi. Full Delphi suite menjalankan 1650 dari 1650 test pada Win32 dan Win64 setelah write side masuk

Parameter scene Chart3d dan jebakan fAuto

Dua detail yang berdekatan menjebak orang yang melampaui chart default. Pertama adalah Chart3d ($103A, [MS-XLS] 2.4.46), payload datar 14 byte yang diterbitkan di dalam chart group untuk varian 3D: anRot (rotation, 0 sampai 360), anElev (elevation, signed, -90 sampai 90), pcDist (perspective distance, 0 sampai 100, diabaikan kecuali fPerspective diset), pcHeight dan pcDepth (persen dari chart width, 5 sampai 500), pcGap (0 sampai 500), dan grbit dengan bit fPerspective $0001, fCluster $0002, fAutoscale $0004, f3DScaling $0010, dan f2DWalls $0020. Spesifikasi menambahkan constraint yang tidak akan ditegakkan oleh record layout untuk Anda: pada transposed bar chart, anRot dan anElev tidak boleh melebihi 44, dan pada pie chart anElev tidak boleh negatif

Yang kedua adalah bit fAuto, dan bit inilah yang menghasilkan laporan bug "warna saya diabaikan". LineFormat ($1007), AreaFormat ($100A), dan MarkerFormat ($1009) semuanya membawa fAuto di bit 0 grbit, dan ketika bit tersebut diset Excel menerapkan automatic style serta memperlakukan RGB eksplisit, line style, marker shape, dan marker size di sebelahnya sebagai hiasan. Emitter yang menulis custom series style harus menghapus bit 0; default emitter membiarkannya diset agar Excel memilih palette. Jika Anda mengedit workbook yang sudah ada, bukan membangun yang baru, aturan preservation kembali berbeda dan dibahas dalam mengedit Excel chart tanpa kehilangan ChartML yang dipertahankan

Secondary axis group, binding SerToCrt, dan typed chart model yang ditunjukkan di sini tersedia dalam HotXLS Delphi spreadsheet component untuk Delphi dan C++Builder, yang membaca dan menulis chart BIFF8 tanpa Excel terpasang; product page memuat referensi chart record lengkap dan daftar overload AddChartSheet