HotPDF tidak memiliki objek bagan. Tidak ada TPDFChart, tidak ada AddBarSeries, tidak ada yang mengambil array angka dan mengembalikan grafik yang sudah dirender. Yang diberikannya sebagai gantinya adalah kanvas halaman dengan kosakata tingkat rendah yang sama yang digunakan setiap model gambar PDF: persegi panjang, garis, lingkaran, isian, garis luar, dan teks yang ditempatkan pada koordinat yang tepat. Bagan dalam dokumen HotPDF karena itu adalah sesuatu yang Anda bangun, bukan sesuatu yang Anda minta. Itu terdengar seperti lebih banyak pekerjaan dari yang sebenarnya. Setelah Anda menulis matematika koordinatnya sekali, bagan batang adalah loop atas persegi panjang, bagan garis adalah polyline, dan bagan pai adalah kipas busur, dan Anda mengontrol setiap piksel hasilnya
Ini penting karena alternatif yang pertama kali dijangkau orang, meraster kontrol bagan layar ke bitmap dan menempelkan gambar ke halaman, memberi Anda bagan yang terkunci ke resolusi layar yang tercetak kabur dan membengkakkan file. Menggambar bagan dengan primitif vektor HotPDF menjaga keluaran tetap tajam pada zoom dan DPI cetak apa pun, karena batang dan sumbunya adalah operator jalur PDF yang nyata, bukan piksel. Harganya adalah Anda memiliki tata letaknya. Mekaniknya menyempit pada beberapa langkah: satu pembalikan koordinat yang menjebak semua orang, bagan batang yang sudah dikerjakan, trik polyline untuk bagan garis, dan matematika busur untuk irisan pai
Satu-satunya bagian sulit adalah sistem koordinat
Grafik layar menempatkan origin di kiri-atas dengan Y tumbuh ke bawah. PDF melakukan sebaliknya. Origin berada di sudut kiri-bawah halaman dan Y tumbuh ke atas, diukur dalam poin (1/72 inci). Setiap panggilan gambar di HotPDF, TextOut, Rectangle, MoveTo, LineTo, Circle, menggunakan konvensi kiri-bawah, Y-naik itu. Jika Anda membawa insting grafik-layar, bagan pertama Anda tergambar terbalik dan keluar dari bagian bawah halaman
Jadi pekerjaan nyata dalam bagan apa pun adalah satu pemetaan: ubah nilai data menjadi koordinat Y yang menghormati Y-naik. Putuskan sebuah persegi panjang plot, empat angka untuk area tempat bagan berada, lalu petakan nilai terkecil dalam data Anda ke tepi bawah dan yang terbesar ke atas. Untuk batang bernilai V pada skala yang berjalan dari 0 ke MaxValue, tepi atas batang adalah PlotBottom + (V / MaxValue) * PlotHeight, dan batang tumbuh ke atas dari PlotBottom. Dapatkan satu ekspresi itu dengan benar dan selebihnya adalah pembukuan. Pembantu di bawah ini menahan geometri plot dan melakukan konversi, sehingga kode gambar tidak pernah menyentuh aritmetika mentah dua kali:
type
TPlotArea = record
Left, Bottom, Width, Height: Single; // poin PDF, origin kiri-bawah
MaxValue: Single; // puncak skala nilai
end;
// Petakan nilai data ke koordinat Y-nya di dalam plot, Y bertambah ke atas.
function ValueToY(const Plot: TPlotArea; V: Single): Single;
begin
Result := Plot.Bottom + (V / Plot.MaxValue) * Plot.Height;
end;
Satu keputusan penilaian bersembunyi dalam MaxValue. Jika Anda menetapkannya ke titik data terbesar yang persis, batang tertinggi menyentuh tepi atas plot dan terlihat terpotong. Bulatkan ke atas ke angka bersih di atas maksimum, misalnya kelipatan 10 atau 100 berikutnya, agar bagan memiliki ruang kepala dan label garis kisi terbaca sebagai angka bulat daripada berapa pun ke puncaknya data kebetulan
Bagan batang adalah loop atas persegi panjang
Dengan pemetaan diselesaikan, bagan batang menulis sendiri. Bagi lebar plot menjadi satu slot per kategori, tinggalkan celah antar batang agar tidak bersentuhan, dan gambar setiap batang sebagai persegi panjang terisi yang tingginya berasal dari ValueToY. Rectangle HotPDF mengambil sudut kiri-bawah ditambah lebar dan tinggi, yang berbaris persis dengan batang yang tumbuh ke atas dari garis dasar. Setel warna isian dulu, bentangkan jalurnya, lalu panggil Fill untuk mengecatnya. Label kategori berada di bawah garis dasar, nilai di atas batang:
procedure DrawBarChart(Page: THPDFPage; const Plot: TPlotArea;
const Values: array of Single; const Labels: array of string);
var
I, Count: Integer;
SlotW, BarW, BarX, BarH, Gap: Single;
begin
Count := Length(Values);
SlotW := Plot.Width / Count;
Gap := SlotW * 0.25; // celah seperempat-slot di setiap sisi
BarW := SlotW - Gap;
// Garis dasar (sumbu X) di sepanjang bawah plot.
Page.SetLineWidth(1.0);
Page.MoveTo(Plot.Left, Plot.Bottom);
Page.LineTo(Plot.Left + Plot.Width, Plot.Bottom);
Page.Stroke;
Page.SetFont('Arial', [], 9);
for I := 0 to Count - 1 do
begin
BarX := Plot.Left + I * SlotW + Gap / 2;
BarH := ValueToY(Plot, Values[I]) - Plot.Bottom;
Page.SetRGBFillColor(RGB(56, 110, 219));
Page.Rectangle(BarX, Plot.Bottom, BarW, BarH); // X, Y, Width, Height
Page.Fill;
// Label kategori di bawah garis dasar, nilai di atas batang.
Page.SetRGBFillColor(clBlack);
Page.TextOut(BarX, Plot.Bottom - 14, 0, Labels[I]);
Page.TextOut(BarX, Plot.Bottom + BarH + 4, 0,
FormatFloat('0', Values[I]));
end;
end;
Dua detail layak dipertahankan. Gap adalah pecahan dari slot alih-alih hitungan titik tetap, sehingga batang tetap berjarak proporsional apakah Anda memplot empat kategori atau empat puluh. Dan label nilai diposisikan dengan tinggi turunan ValueToY yang sama yang digunakan batang, sehingga selalu duduk tepat di atas batangnya sendiri alih-alih mengambang pada offset yang ditebak. Jika Anda ingin garis kisi horizontal di belakang batang, gambar sebelum loop: pilih tiga atau empat nilai bulat, jalankan ValueToY pada masing-masing, dan gores garis samar melintasi plot pada Y itu. Menggambarnya lebih dulu menempatkannya di belakang batang dalam tumpukan model-pelukis yang digunakan PDF
Sumbu, tanda centang, dan label hanyalah garis dan teks lagi
Bagan belum selesai sampai pembaca bisa tahu apa arti batangnya, dan itu seluruhnya pekerjaan sumbu. Sumbu vertikal adalah satu garis yang digores ke atas di sepanjang tepi kiri plot dengan segelintir tanda centang dan nilainya. Gunakan kembali ValueToY agar tanda centang mendarat pada skala yang sama yang digunakan batang, jika tidak batang dan garis kisinya tidak setuju dan bagan berbohong diam-diam:
procedure DrawValueAxis(Page: THPDFPage; const Plot: TPlotArea;
TickCount: Integer);
var
I: Integer;
TickV, TickY: Single;
begin
Page.SetLineWidth(1.0);
Page.MoveTo(Plot.Left, Plot.Bottom);
Page.LineTo(Plot.Left, Plot.Bottom + Plot.Height);
Page.Stroke;
Page.SetFont('Arial', [], 8);
for I := 0 to TickCount do
begin
TickV := (Plot.MaxValue / TickCount) * I;
TickY := ValueToY(Plot, TickV);
Page.MoveTo(Plot.Left - 4, TickY); // tanda centang pendek di luar sumbu
Page.LineTo(Plot.Left, TickY);
Page.Stroke;
Page.TextOut(Plot.Left - 30, TickY - 3, 0, FormatFloat('0', TickV));
end;
end;
Label adalah tempat bagan paling sering pecah dalam produksi, dan kegagalannya selalu sama: teks yang pas di layar melampaui ruangnya dalam PDF. Nama kategori yang panang bertabrakan dengan tetangganya, dan nama bulan yang dilokalkan seperti "septembre" atau "Dezember" lebih lebar daripada "Sep" bahasa Inggris yang Anda uji. Tidak ada autosize untuk menyelamatkan Anda di sini, jadi tinggalkan margin nyata di bawah garis dasar, kecilkan font satu atau dua poin untuk set kategori yang padat, dan jika nama benar-benar panang, rotasikan. TextOut mengambil sudut sebagai argumen ketiganya, jadi meneruskan 90 mendirikan label dan memberi Anda ruang tanpa tumpang tindih. Uji tata letak dengan label terlebar yang Anda harapkan, bukan yang terpendek, sebelum ekspor dikirim
Bagan garis: satu polyline melalui titik yang dipetakan
Bagan garis menggunakan kembali seluruh pemetaan nilai dan hanya mengubah cara titik terhubung. Alih-alih persegi panjang per kategori, Anda berjalan melalui data sekali, mengubah setiap nilai ke (X, Y)-nya dengan ValueToY, dan menjahit titik bersama dengan satu MoveTo diikuti panggilan LineTo, digores di akhir. Titik pertama membuka jalur; setiap titik berikutnya memperluasnya:
procedure DrawLineChart(Page: THPDFPage; const Plot: TPlotArea;
const Values: array of Single);
var
I, Count: Integer;
StepX, X, Y: Single;
begin
Count := Length(Values);
if Count < 2 then Exit;
StepX := Plot.Width / (Count - 1);
Page.SetLineWidth(1.5);
Page.SetRGBStrokeColor(RGB(214, 92, 36));
for I := 0 to Count - 1 do
begin
X := Plot.Left + I * StepX;
Y := ValueToY(Plot, Values[I]);
if I = 0 then
Page.MoveTo(X, Y) // buka jalur pada titik pertama
else
Page.LineTo(X, Y); // perluas melalui setiap titik berikutnya
end;
Page.Stroke; // satu stroke mengecat seluruh polyline
end;
Perhatikan perbedaan spasi. Bagan batang membagi lebar dengan jumlah batang, karena setiap batang memiliki slot. Bagan garis membagi dengan jumlah interval, Count - 1, karena titik pertama dan terakhir duduk di tepi plot dan garis membentang melintasi celah di antaranya. Mencampur kedua itu adalah alasan biasa bagan garis menyimpang setengah-slot dari bagan batang yang seharusnya dilapisinya. Jika Anda ingin penanda di setiap titik data, jatuhkan Circle kecil dan Fill di setiap (X, Y) setelah polyline digores
Bagan pai: busur, atau baji jika Anda menjaganya sederhana
Irisan pai adalah satu bentuk yang membutuhkan trigonometri, karena baji dibatasi oleh dua jari-jari dan sebuah busur. Versi jujur menyapu busur dengan melangkah segmen garis kecil di sepanjang keliling, yang menghampiri kurva cukup dekat sehingga tidak ada pembaca yang bisa tahu. Sudut sapuan setiap iris adalah bagiannya dari total, (Value / Total) * 2π, dan Anda mengakumulasi sudut berjalan saat Anda berjalan mengelilingi:
procedure DrawPieChart(Page: THPDFPage; CX, CY, Radius: Single;
const Values: array of Single; const Colors: array of TColor);
var
I, Step, Steps: Integer;
Total, Start, Sweep, A: Single;
begin
Total := 0;
for I := 0 to High(Values) do Total := Total + Values[I];
Start := 0;
for I := 0 to High(Values) do
begin
Sweep := (Values[I] / Total) * 2 * Pi;
Steps := Round(Sweep / (Pi / 90)) + 1; // ~2 derajat per segmen
Page.SetRGBFillColor(Colors[I]);
Page.MoveTo(CX, CY); // puncak baji di pusat
for Step := 0 to Steps do
begin
A := Start + Sweep * (Step / Steps);
Page.LineTo(CX + Radius * Cos(A), CY + Radius * Sin(A));
end;
Page.LineTo(CX, CY); // tutup kembali ke pusat
Page.Fill;
Start := Start + Sweep; // maju ke irisan berikutnya
end;
end;
Jalur dari-pusat-keluar, puncak lebih dulu, lalu busur, lalu kembali ke puncak, memberi baji tertutup yang Fill cat padat. Hitungan segmen memperdagangkan kehalusan dengan ukuran jalur: kira-kira dua derajat per langkah terlihat bulat pada jari-jari yang wajar tanpa menghasilkan aliran konten yang sangat besar. Jika Anda tidak membutuhkan lingkaran sejati, Anda bisa melewati trig sepenuhnya dan merender data yang sama sebagai satu batang bertumpuk horizontal, lebar setiap segmen proporsional terhadap bagiannya. Itu sering lebih terbaca daripada pai, dan itu hanya kode batang dengan persegi panjang disusun ujung-ke-ujung. Jangkau versi busur hanya ketika desain memanggil lingkaran aktual
Tidak satu pun dari ini bergantung pada pustaka bagan apa pun yang terpasang, yang merupakan keuntungan diam dari menggambar primitif secara langsung. Panggilan gambar kanvas yang sama yang menempatkan logo atau kotak tanda tangan membangun bagan ini, dan TextOut yang sama yang memberi label pada kolom formulir memberi label pada sumbu. Letakkan geometri plot dalam sebuah record, petakan nilai ke Y sekali, dan bagan batang, garis, atau pai adalah rutin pendek atas Rectangle, LineTo, dan Circle yang bisa Anda jatuhkan ke laporan apa pun. Panggilan Rectangle, MoveTo, LineTo, Circle, Fill, Stroke, dan TextOut yang digunakan di sini adalah bagian dari HotPDF Delphi Component untuk Delphi dan C++Builder