Teknisk artikkel

Generere stolpediagrammer i PDF fra Delphi: Aritmetikken

Når en Delphi-applikasjon må bygge inn et diagram i en PDF, er det typiske instinktet å tegne det på skjermen med TeeChart eller en annen UI-komponent, dumpe det til en skjult bitmap, og bygge inn rasterbildet i dokumentet. Du betaler prisen tre ganger: koden er avhengig av to store biblioteker i stedet for ett, PDF-en blåses opp fra bildeutkastet, og diagramlinjene er uskarpe når leseren zoomer inn eller skriver dem ut

Bortsett fra 3D-rotasjon og komplekse overflater, er et forretningsdiagram ingenting annet enn fylte rektangler, strekede linjer og sentrert tekst. En vektor-PDF-komponent kan tegne alle tre med sine egne tegnekall direkte til sidens innholdsstrøm. Resultatet bærer ingen unødvendig oppløsning, forblir uendelig skarpt og kjører raskere fordi det ikke går gjennom Windows GDI-skjermkontekster. Den eneste barrieren er at du må skrive de førti linjene med skaleringsmatematikk for å kartlegge datarommet ditt til papirrommet

Kartlegge verdier til poeng

Problemet koker ned til én skaleringfaktormultiplikator: hvor mange punkter på siden (1/72 tomme) tilsvarer én enhet av dataene dine. Finn ut maksimumsverdien din, bestem hvor høyt du vil at diagrammet skal være på siden, og del. Alt annet, rutenettlinjene, stolpene, etikettene, stammer fra det ene forholdet

var
  Pdf: THotPDF;
  Data: array[0..5] of Double;
  Labels: array[0..5] of string;
  I: Integer;
  
  // Diagrammets oppsettbegrensninger, målt i PDF-punkter (1/72 tomme)
  ChartLeft, ChartBottom: Double;
  ChartWidth, ChartHeight: Double;
  BarWidth, GapWidth, XPos: Double;
  
  // Dataskalering
  MaxValue, ScaleFactor: Double;
  GridStep, Val, YPos: Double;
begin
  // Noen faste eksempeldata
  Data[0] := 450.5; Labels[0] := 'Jan';
  Data[1] := 820.0; Labels[1] := 'Feb';
  Data[2] := 610.2; Labels[2] := 'Mar';
  Data[3] := 1150.8; Labels[3] := 'Apr';
  Data[4] := 920.0; Labels[4] := 'May';
  Data[5] := 1340.5; Labels[5] := 'Jun';

  // 1. Finn datamaskimumet for å skalere Y-aksen
  MaxValue := 0;
  for I := Low(Data) to High(Data) do
    if Data[I] > MaxValue then
      MaxValue := Data[I];

  // Avrund den oppradert til nærmeste pene antall (f.eks. til nærmeste 500)
  // Hardkoding for eksemplet; i produksjon, velg dette dynamisk
  MaxValue := 1500; 

  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'VectorChart.pdf';
    Pdf.BeginDoc;

    // 2. Definer diagrammets avgrensningsboks på siden.
    // HotPDFs (0,0) er nede til venstre.
    ChartLeft := 72;        // 1 tomme fra venstre
    ChartBottom := 400;     // omtrent midt på siden
    ChartWidth := 400;
    ChartHeight := 200;

    // Aritmetikken for å gjøre data til punkter
    ScaleFactor := ChartHeight / MaxValue;

    // (...) Fortsett til tegning

Skaleringfaktoren lar deg ta hvilken som helst verdi og vite nøyaktig hvor høyt over bunnen av diagrammet den faller (Verdi * ScaleFactor). Dette kobler tegnekoden din fra domenets skala; uansett om du plotter titalls millimeter eller millioner av dollar, sørger multiplikatoren for at den øverste kanten nøyaktig fyller plassen på 200 punkter du tildelte den

Tegne det statiske rutenettet først

Mennesker kan ikke anslå barhøyder uten referanselinjer. Disse bakgrunnslinjene går inn før dataene, malt opp med et lyst strekmønster slik at de ikke konkurrerer visuelt med informasjonen

    // 3. Tegn bakgrunnsrutenettet
    Pdf.CurrentPage.SetLineWidth(0.5);
    Pdf.CurrentPage.SetRGBStrokeColor(RGB(200, 200, 200)); // Lysegrå
    Pdf.CurrentPage.SetFont('Helvetica', [], 9);

    GridStep := 500; // Tegn en linje for hver 500. enhet
    Val := 0;
    while Val <= MaxValue do
    begin
      // Skaler verdien for å finne dens Y-offset, legg deretter til bunnen av diagrammet
      YPos := ChartBottom + (Val * ScaleFactor);

      // Den stiplede horisontale linjen
      Pdf.CurrentPage.SetDash([2, 2], 0); // 2 av, 2 på
      Pdf.CurrentPage.MoveTo(ChartLeft, YPos);
      Pdf.CurrentPage.LineTo(ChartLeft + ChartWidth, YPos);
      Pdf.CurrentPage.Stroke;

      // Y-akse etiketten til venstre for rutenettet
      Pdf.CurrentPage.SetDash([], 0); // Tilbakestill til heltrekt for tekst 
      // Juster Y-posisjonen for teksten litt nedover for vertikal sentrering
      Pdf.CurrentPage.TextOut(ChartLeft - 30, YPos - 3, 0, FloatToStr(Val));

      Val := Val + GridStep;
    end;

Pennen-tilstanden administreres bevisst her. SetDash tar et array som definerer mønsteret; [2, 2] gjør rutenettet stiplet. Du må eksplisitt tømme den (ved å sende et tomt array [] eller kalle NoDash) før påfølgende geometri, ellers vil hver form på siden arve stiplet kantlinje

Iterere og plassere barene

Stolper i et søylediagram trenger pusterom. Avstanden beregnes ofte som et forhold (ratio) av stolpebredden, men i faste oppsett er det lettere å dele den tilgjengelige X-rommet i celler (én for hver datapunkt) og sentrere baren inni

    // 4. Tegn stolpene og X-akseetikettene
    BarWidth := (ChartWidth / Length(Data)) * 0.7; // Bruker 70 % av cellevidden
    GapWidth := (ChartWidth / Length(Data)) * 0.3; 
    
    // Start X ved den første cellens kant
    XPos := ChartLeft + (GapWidth / 2);

    Pdf.CurrentPage.SetLineWidth(1);
    Pdf.CurrentPage.SetRGBStrokeColor(RGB(0, 50, 100));     // Mørkeblå ramme
    Pdf.CurrentPage.SetRGBFillColor(RGB(100, 150, 220));    // Lyseblått fyll

    for I := Low(Data) to High(Data) do
    begin
      // Beregn barens høyde i punkter
      YPos := Data[I] * ScaleFactor;

      // Baren er et rektangel. Koordinatene er X, Y (nede til venstre), Bredde, Høyde
      Pdf.CurrentPage.Rectangle(XPos, ChartBottom, BarWidth, YPos);
      Pdf.CurrentPage.FillAndStroke;

      // X-akse-etiketten (sentrert under baren)
      // HotPDF har ikke et direkte sentrert TextOut-kall, så du kan enten tilnærme
      // det, eller bruke GetTextWidth for å beregne et nøyaktig X-offset.
      // Her plasserer vi den grovt
      Pdf.CurrentPage.TextOut(XPos + (BarWidth / 2) - 10, ChartBottom - 15, 0, Labels[I]);

      // Verdietiketten rett på toppen av baren
      Pdf.CurrentPage.TextOut(XPos + (BarWidth / 2) - 12, ChartBottom + YPos + 5, 0, FloatToStr(Data[I]));

      // Gå frem til neste celle
      XPos := XPos + BarWidth + GapWidth;
    end;

    // 5. En hel baseline for X-aksen for å forankre det hele
    Pdf.CurrentPage.SetRGBStrokeColor(clBlack);
    Pdf.CurrentPage.MoveTo(ChartLeft, ChartBottom);
    Pdf.CurrentPage.LineTo(ChartLeft + ChartWidth, ChartBottom);
    Pdf.CurrentPage.Stroke;

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Rectangle-metoden forventer punktet nede til venstre (XPos, ChartBottom), deretter bredden (BarWidth) og høyden (YPos). Fordi PDF-koordinatene vokser oppover, bygger baren seg naturlig fra X-aksen slik øyet forventer, uten subtraksjonsgeometrien fra bunnen av siden som et nedadgående system ville krevd

Sluttresultatet bygger et standard kolonnediagram. Ved å justere logikken her fra X til Y og omvendt, gjør den samme kartleggingen til horisontale streker (bar chart). Hvis du plotter linjer (tidsserier), dropper du Rectangle og bruker den samme beregningen for å mate en serie med MoveTo / LineTo-kall. Åpne PDF-utgangen, og baren vil zoome skarpt for alltid

Kallene for vektortegning, MoveTo, LineTo, Rectangle, SetDash og FillAndStroke vist her er en del av HotPDF-komponenten for Delphi og C++Builder