[PHP] Staafdiagrammen hebben verschillende breedtes

Pagina: 1
Acties:
  • 244 views sinds 30-01-2008
  • Reageer

Onderwerpen


Acties:
  • 0 Henk 'm!

  • Reveller
  • Registratie: Augustus 2002
  • Laatst online: 05-12-2022
Ik heb de volgende code om een staafdiagram te maken (eea zo goed mogelijk gedocumenteerd):
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
function stats_bar($title, $data, $imagename) {
  $width   = 530; 
  $height  = 200; 
  $vmargin = 20; // top + bottom margin for x-labels 
  $hmargin = 30; // left margin for y-labels 
  $ngrid   = 5;  // number of grid lines

  $image  = imagecreate($width, $height); 
  $white  = imagecolorallocate($image, 0xFF, 0xFF, 0xFF); 
  $navy   = imagecolorallocate($image, 0x00, 0x00, 0x80); 
  $black  = imagecolorallocate($image, 0x00, 0x00, 0x00); 
  $gray   = imagecolorallocate($image, 0xC0, 0xC0, 0xC0);
 
  $nval   = sizeof($data); 
  $base   = floor(($width - $hmargin) / $nval); // distance between columns 
  $ysize  = $height - (2 * $vmargin);           // y-size of plot 
  $xsize  = $nval * $base;                      // x-size of plot 
 
  $txtsz  = imagefontwidth(2) * strlen($title); // pixel-width of title 
  $xpos   = max(1, $hmargin);                   // force positive coordinates 
  
  imagestring($image, 2, $xpos, 0, $title , $black); 

  $dydat   = max($data) / $ngrid;   // data units between grid lines 
  $dypix   = $ysize / ($ngrid + 1); // pixels between grid lines 
  $padding = 2;                                // 1/2 spacing between columns 
  $yscale  = $ysize / (($ngrid + 1) * $dydat); // pixels per data unit 
  
  for ($i = 0; $i <= ($ngrid); $i++) { // $ngrid + 1 voor extra y-label
    $ydat  = (int)($i * $dydat); 
    $ypos  = $vmargin + $ysize - (int)($i * $dypix); 
    $txtsz = imagefontwidth(1) * strlen($ydat); // pixel-width of label 
    $txtht = imagefontheight(1);                // pixel-height of label  
    $xpos  = max(1, (int)(($hmargin - $txtsz) / 2)); 

    imagestring($image, 1, $xpos, $ypos - (int)($txtht / 2), $ydat, $black); 

    if (!($i == 0) && !($i > $ngrid)) { 
      imagelinedotted($image, $hmargin, $ypos, $hmargin + $xsize, $ypos, 2, $gray);
    }
  } 

  imagerectangle($image, $hmargin, $vmargin, $hmargin + $xsize, $vmargin + $ysize, $black); 

  for ($i = 0; list($xval, $yval) = each($data); $i++) { 
    $ymax = $vmargin + $ysize; 
    $ymin = $ymax - (int)($yval * $yscale); 
    $xmax = $hmargin + ($i + 1) * $base - $padding; 
    $xmin = $hmargin + $i * $base + $padding; 

    imagefilledrectangle($image, $xmin, $ymin, $xmax, $ymax, $navy); 

    $txtsz = imagefontwidth(1) * strlen($xval); 
    $xpos  = $xmin + (int)(($base - $txtsz) / 2); 
    $xpos  = max($xmin, $xpos); 
    $ypos  = $ymax + 6; // distance of x-labels from x axis 

    imagestring($image, 1, $xpos, $ypos, $xval, $black); 
  }

  imagepng($image, IMG_PATH . $imagename . '.png'); 
  imagedestroy($image);
}

// Voorbeeldcode om functie te gebruiken:
for ($i = 1; $i < 8; $i++) {
  $dag[$i] = (3 * $i) + 5;
}
stats_bar("Verdeling over dagen van de week", $dag, 'dagelijks');
Het probleem is als volgt: in regel 15 gebruik ik floor om de breedte van de kolommen te berekenen. Ik gebruik floor, omdat ik de breedte in hele pixels wil hebben. Als ik ceil gebruik, kan de totale breedte van de kolommen meer zijn dan de breedte van het plaatje zelf ($width = 530 in regel 2). Het grote nadeel van floor is, dat als er veel kolommen zijn, de waarde van $base telkens een halve pixel ofzo kleiner is dan de werkelijke (niet-gefloorde) breedte. Aan het einde van de rit mis ik dan 10 pixels ofzo. Met andere woorden: het plaatje wordt minder breed dan 530 pixels. Samengevat:
  • floor zorgt bij veel kolommen voor grafieken smaller dan 530 px
  • ceil zorgt bij veel kolommen voor grafieken breder dan 530 px
Als ik ipv floor of ceil, round gebruik, dan gaat dit minder vaak fout, maar nog steeds krijg ik dan plaatjes die net een pixel te breed of te smal zijn:
PHP:
7
$base = round((($width - $hmargin) / $nval), 1); // width of columns 

Een aantal voorbeelden:

Afbeeldingslocatie: http://www.danandan.luna.nl/got/dagelijks.png
Afbeeldingslocatie: http://www.danandan.luna.nl/got/wekelijks.png
Afbeeldingslocatie: http://www.danandan.luna.nl/got/maandelijks.png
Afbeeldingslocatie: http://www.danandan.luna.nl/got/jaarlijks.png
Vraag: hoe kan ik ervoor zorgen dat alle plaatjes even breed zijn? Ik heb er al aan gedacht om de padding tussen de kolommen automatisch te maken ipv fixed (maar dan zit ik nog steeds met het afrond-probleem). De enige andere oplossing die ik zelf kon bedenken was om alle "verloren" pixels op te tellen en tussen de laatste kolom en de zwarte rechterkant van het kader, een marge / witruimte ter grootte van die pixels te stoppen. Heeft iemand nog een ander idee?

[ Voor 6% gewijzigd door Reveller op 05-05-2006 16:41 ]

"Real software engineers work from 9 to 5, because that is the way the job is described in the formal spec. Working late would feel like using an undocumented external procedure."


Acties:
  • 0 Henk 'm!

  • André
  • Registratie: Maart 2002
  • Laatst online: 12-09 14:32

André

Analytics dude

Aangezien je alles afrond blijf je dit houden en kun je het alleen corrigeren door middel van je laatstgenoemde oplossing: alle getallen achter de komma optellen en als ruimte aan de rechterkant toevoegen.

Acties:
  • 0 Henk 'm!

  • NMe
  • Registratie: Februari 2004
  • Laatst online: 09-09 13:58

NMe

Quia Ego Sic Dico.

Sowieso is het hier handiger round() te gebruiken in plaats van floor()/ceil(). Vervolgens kun je zelf marges instellen die je in je code naleeft. Je kan nooit exact breed genoeg zijn aangezien je geen halve pixels kunt afdrukken. Gewoon een vaste afronding doen en de rest opvullen met marges.

'E's fighting in there!' he stuttered, grabbing the captain's arm.
'All by himself?' said the captain.
'No, with everyone!' shouted Nobby, hopping from one foot to the other.


Acties:
  • 0 Henk 'm!

  • Genoil
  • Registratie: Maart 2000
  • Laatst online: 12-11-2023
Ik heb je script niet goed bestudeerd, maar stel dat je je bars van links naar rechts tekent. Dan houdt je zowel de ideale als de echte x positie bij waarop je "tekenpen" staat en bepaalt aan de hand daarvan of je floor of ceil gebruikt:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?
$n      = rand(1,100);
$w      = 530;
$fdx    = $w / $n;

$actual = 0;
$ideal  = 0; 
for($i = 0; $i < $n ; $i++) {
    $dx = $actual > $ideal ? floor($fdx) : ceil($fdx);
    $actual += $dx;
    $ideal  += $fdx;
    echo $dx."<br>";
}
echo $actual;
?>


$dx is de breedte van de n-de bar en $actual komt altijd uit op 530. uiteraard zijn dan niet alle bars even breed. Je kunt er dan voor kiezen of je het verschil oplost in kleinere / grotere marges of in de breedte van de balk...

[ Voor 35% gewijzigd door Genoil op 05-05-2006 17:07 ]


Acties:
  • 0 Henk 'm!

  • killercow
  • Registratie: Maart 2000
  • Laatst online: 18-09 12:47

killercow

eth0

ik ben al een tijdje afgestapt van het serverside generaten van bargraphs, met css kan het ook zonder problemen.

hier een voorbeeldje:

http://apples-to-oranges.com/blog/article.aspx?id=55

scheelt je behoorlijk wat serverside cpu last, onhandige code, en vergroot de usability voor mensen, ze kunnen nu immers copy pasten en zo de onderliggende data in een spreadsheet progje gooien.

nog wat voorbeeldjes:

http://concepts.waetech.com/bargraph/

En vooral deze is erg stoer:
http://www.meyerweb.com/eric/css/edge/bargraph/demo.html

[ Voor 17% gewijzigd door killercow op 05-05-2006 22:40 ]

openkat.nl al gezien?