HotPDF nema objekt grafikona. Ovdje ne postoji TPDFChart, kao ni funkcija AddBarSeries ili išta slično što jednostavno preuzima polje brojeva i vraća iscrtani grafikon. Ono što umjesto toga dobivate jest radna površina stranice s istim vokabularom niske razine koji koristi svaki model crtanja PDF-a: pravokutnici, linije, krugovi, ispune, potezi i tekst smješteni na točnim koordinatama. Grafikon u HotPDF dokumentu je, stoga, nešto što sami gradite, a ne nešto što tražite. To zvuči kao više posla nego što zaista jest. Nakon što jednom napišete matematiku koordinata, stupčasti grafikon postaje samo petlja nad pravokutnicima, linijski grafikon je polilinija, a tortni grafikon lepeza lukova, dok vi u potpunosti kontrolirate svaki piksel rezultata
Ovo je važno jer prva alternativa za kojom ljudi posežu, rasteriziranje kontrole zaslonskog grafikona u bitmapu i lijepljenje slike na stranicu, rezultira grafikonom koji je zaključan na zaslonsku rezoluciju, mutan pri ispisu i nepotrebno povećava veličinu datoteke. Crtanje grafikona s pomoću HotPDF vektorskih osnovnih oblika zadržava izlaz jasnim pri bilo kojem povećanju i svakoj DPI rezoluciji ispisa jer su stupci i osi pravi PDF operatori staza, a ne pikseli. Cijena toga jest da vi upravljate rasporedom. Mehanika se svodi na nekoliko osnovnih poteza: jedan obrat koordinata s kojim se svi bore, razrađeni stupčasti grafikon, trik s polilinijom za linijske grafikone i matematika luka za kriške torte
Jedini težak dio je koordinatni sustav
Zaslonska grafika postavlja ishodište u gornji lijevi kut s Y koordinatom koja raste prema dolje. PDF radi upravo suprotno. Ishodište leži u donjem lijevom kutu stranice, a Y koordinata raste prema gore i mjeri se u točkama (1/72 inča). Svaki poziv za crtanje u HotPDF-u, TextOut, Rectangle, MoveTo, LineTo, Circle, koristi tu donju-lijevu, Y-prema-gore konvenciju. Ako prenesete instinkte zaslonske grafike, vaš prvi grafikon bit će nacrtan naopako i izaći će s dna stranice
Pravi zadatak u bilo kojem grafikonu je jedno mapiranje: pretvaranje podatkovne vrijednosti u Y koordinatu koja poštuje to pravilo. Odlučite se za pravokutnik crteža (četiri broja za područje unutar kojeg će grafikon biti), a zatim mapirajte najmanju vrijednost vaših podataka na donji rub, a najveću na vrh. Za stupac vrijednosti V na ljestvici koja ide od 0 do MaxValue, gornji rub stupca bit će PlotBottom + (V / MaxValue) * PlotHeight, dok stupac raste prema gore od PlotBottom. Pogodite taj jedan izraz i sve ostalo bit će samo jednostavno bilježenje. Sljedeći pomoćnik sadrži geometriju grafa i vrši konverziju kako kôd za crtanje ne bi dvaput dodirivao istu aritmetiku:
type
TPlotArea = record
Left, Bottom, Width, Height: Single; // PDF points, bottom-left origin
MaxValue: Single; // top of the value scale
end;
// Map a data value to its Y coordinate inside the plot, Y growing upward.
function ValueToY(const Plot: TPlotArea; V: Single): Single;
begin
Result := Plot.Bottom + (V / Plot.MaxValue) * Plot.Height;
end;
Jedna sitnica krije se u MaxValue. Ako ga postavite na točno najveću točku u podacima, najviši stupac dodirnut će gornji rub grafa i izgledat će odrezano. Zaokružite ga na čisti broj iznad samog maksimuma, na primjer, na sljedeći višekratnik od 10 ili 100, tako da grafikon ima prostora i oznake rešetki izgledaju kao zaokružene brojke, a ne kao nasumične brojke ovisne o najvećoj vrijednosti
Stupčasti grafikon je petlja nad pravokutnicima
Nakon što riješite mapiranje, stupčasti se grafikon piše sam. Podijelite širinu grafa u jedan utor po kategoriji, ostavite razmak između stupaca kako se ne bi dodirivali i nacrtajte svaki stupac kao ispunjeni pravokutnik čija visina dolazi iz funkcije ValueToY. HotPDF funkcija Rectangle uzima donji lijevi kut zajedno sa širinom i visinom, što se točno podudara sa stupcem koji raste prema gore od osnovne linije. Prvo postavite boju ispune, položite stazu, a zatim pozovite Fill kako biste je obojili. Oznaka kategorije ide ispod osnovne linije, a vrijednost iznad stupca:
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; // quarter-slot gap on each side
BarW := SlotW - Gap;
// Baseline (the X axis) along the bottom of the 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;
// Category label below the baseline, value above the bar.
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;
Dva detalja ovdje opravdavaju svoje postojanje. Razmak (Gap) je udio utora, a ne fiksni broj točaka, tako da stupci ostaju proporcionalno raspoređeni bilo da crtate četiri kategorije ili njih četrdeset. A oznaka vrijednosti smještena je s istom visinom izvedenom iz ValueToY koju koristi stupac, pa uvijek sjedi točno iznad vlastitog stupca umjesto da lebdi u obliku nagađanog pomaka. Ako želite horizontalne mrežne linije iza stupaca, nacrtajte ih prije petlje: odaberite tri ili četiri okrugle vrijednosti, pokrenite ValueToY za svaku i povucite blagu crtu preko grafa na tom Y-u. Njihovo crtanje na početku postavlja ih iza stupaca unutar slikarskog modela slaganja koji koristi PDF
Osi, kvačice i oznake samo su dodatne linije i tekst
Grafikon nije završen sve dok čitatelj ne bude razumio što stupci znače, a to je u potpunosti zadatak osi. Vertikalna os je jedna nacrtana linija uz lijevi rub grafa uz pregršt kvačica i njihovih vrijednosti. Ponovno iskoristite ValueToY kako bi kvačice sletjele na isto mjerilo kao i stupci, jer se u suprotnom stupac i njegova rešetka neće slagati, i grafikon će tiho prikazivati pogreške:
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); // short tick outside the axis
Page.LineTo(Plot.Left, TickY);
Page.Stroke;
Page.TextOut(Plot.Left - 30, TickY - 3, 0, FormatFloat('0', TickV));
end;
end;
Oznake su mjesto gdje grafikoni u produkciji najčešće zakazuju, a pogreška je uvijek ista: tekst koji je dobro pristajao na zaslonu sada preuzima cijeli prostor u PDF-u. Dugi nazivi kategorija sudarat će se sa svojim susjedima, a lokalizirani mjeseci poput "septembre" ili "Dezember" bit će puno širi od engleskog "Sep" s kojim ste testirali. Ovdje ne postoji automatsko skaliranje kako biste se spasili, stoga ostavite dovoljnu marginu ispod osnovne linije, smanjite font za veličinu ili dvije za gušće skupove kategorija i ako su nazivi doista dugi, jednostavno ih rotirajte. TextOut funkcija uzima kut kao svoj treći argument, stoga ako unesete 90 to će podići oznaku i osigurati prostor bez preklapanja. Testirajte raspored sa svojom najširom očekivanom oznakom, a ne najkraćom, i to učinite prije nego što projekt ide u izvoz
Linijski grafikoni: jedna polilinija preko mapiranih točaka
Linijski grafikon koristi čitavo to isto mapiranje vrijednosti te samo mijenja način povezivanja. Umjesto pravokutnika po kategoriji, vi prolazite kroz podatke jednom, pretvarate svaku vrijednost u (X, Y) s ValueToY funkcijom i spajate ih s MoveTo pozivima popraćenim s LineTo pozivima. Prva točka otvara stazu, a svaka naknadna ju dodatno proširuje:
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) // open the path at the first point
else
Page.LineTo(X, Y); // extend it through every later point
end;
Page.Stroke; // one stroke paints the whole polyline
end;
Zabilježite razliku u proredu. Stupčasti grafikon dijeli širinu po broju stupaca, iz razloga što svaki stupac preuzima jedan dio utora. Linijski grafikon se dijeli putem broja samih intervala, Count - 1, to je stoga jer prva i zadnja točka uvijek stoje na granicama grafa. Ukoliko to pomiješate, linijski grafikon vrlo će lako otići napola ukoso prema rubovima u odnosu na grafičke oznake na kojem bi trebao sjediti. Ako vam je potreban marker pri svakoj podatkovnoj točki, stavite manji Circle i Fill pri samom (X, Y) dijelu i to nakon što se polilinija nacrta
Tortni grafikoni: kružni lukovi ili kriške
Kriške tortnog grafikona predstavljaju jedini ovakav oblik u kojem je nužno poznavanje trigonometrije. Razlog tomu jest taj da je sam rub ograničen s dva radijusa kruga te lukom. Iskreniji i direktniji prikaz prebrisat će sami luk tako što povuče mali stupnjeviti polilinijski segment uzduž okvira linije, uz adekvatnu aproksimaciju kružnice. Svaki presjek kuta iznosit će otprilike (Vrijednost / Total) * 2π, pa ćete ukupan obliži kut dobiti ovisno o tom krugu:
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 degrees per segment
Page.SetRGBFillColor(Colors[I]);
Page.MoveTo(CX, CY); // wedge apex at the center
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); // close back to the center
Page.Fill;
Start := Start + Sweep; // advance to the next slice
end;
end;
Putanja od centra prema van, gdje vrh ide prvi, pa onda luk i potom nazad do centra stvara zatvoreni klin kojeg Fill oslikava do pune linije. Računanje po segmentima mijenja samu glatkoću veličine polja. Grubo oko dva stupnja na jedan korak, na bilo kojem boljem i adekvatnijem omjeru dat će dobar ispis, a neće nužno otežati graf. Ako nemate potrebe za cijelim i pravim krugom radije preskočite gornju trigonometriju pa renderirajte taj dio u jedan okrugli sloj. Biti će puno jednostavnije pri pogledu te izgleda čisto naspram nepotrebnih detalja na bazi krugova
Ništa od ovoga nužno ne ovisi o bilo kojem posebnom alatu ni obliku iscrtanog grafikona za dodatno ispisivanje, tu i je pretežita prednost koja izvlači bazu primitiva pri usmjeravanju. Isto sučelje za crtanje baze iscrtava graf i logotip te ih oblikuje redom u grafikon, dok će funkcija s TextOut isto tako izvesti i oznaku polja za sami axis tj osi unosa. Postavite pravila okvira direktno u formu, izvedite putanje od Y, pa odradite jedan kratki prolaz koji objedinjuje parametre Rectangle, LineTo, kao i opciju Circle kod novog izvještaja. Funkcije poput Rectangle, MoveTo, LineTo, Circle, Fill, Stroke, kao i TextOut sastavni su dio HotPDF komponente za Delphi i C++Builder