create a double column graph in fpdf php - php

I'm creating a graph in fpdf and I saw this question and it is exactly the same as what I need but when I copied it on my report-weekly.php file it doesn't work and gave me an error message of
Notice: Undefined offset: 0 in C:\xampp\htdocs\qtqt\asd\report-weekly.php on line 112
Notice: Undefined offset: 1 in C:\xampp\htdocs\qtqt\asd\report-weekly.php on line 112
Notice: Undefined offset: 2 in C:\xampp\htdocs\qtqt\asd\report-weekly.php on line 112
FPDF error: Some data has already been output, can't send PDF file
this is the code I copied
<?php
require('diag/sector.php');
class PDF_Diag extends PDF_Sector {
var $legends;
var $wLegend;
var $sum;
var $NbVal;
function ColumnChart($w, $h, $data, $format, $color=null, $maxVal=0, $nbDiv=4)
{
// RGB for color 0
$colors[0][0] = 155;
$colors[0][1] = 75;
$colors[0][2] = 155;
// RGB for color 1
$colors[1][0] = 0;
$colors[1][1] = 155;
$colors[1][2] = 0;
// RGB for color 2
$colors[2][0] = 75;
$colors[2][1] = 155;
$colors[2][2] = 255;
// RGB for color 3
$colors[3][0] = 75;
$colors[3][1] = 0;
$colors[3][2] = 155;
$this->SetFont('Courier', '', 10);
$this->SetLegends($data,$format);
// Starting corner (current page position where the chart has been inserted)
$XPage = $this->GetX();
$YPage = $this->GetY();
$margin = 2;
// Y position of the chart
$YDiag = $YPage + $margin;
// chart HEIGHT
$hDiag = floor($h - $margin * 2);
// X position of the chart
$XDiag = $XPage + $margin;
// chart LENGHT
$lDiag = floor($w - $margin * 3 - $this->wLegend);
if($color == null)
$color=array(155,155,155);
if ($maxVal == 0)
{
foreach($data as $val)
{
if(max($val) > $maxVal)
{
$maxVal = max($val);
}
}
}
// define the distance between the visual reference lines (the lines which cross the chart's internal area and serve as visual reference for the column's heights)
$valIndRepere = ceil($maxVal / $nbDiv);
// adjust the maximum value to be plotted (recalculate through the newly calculated distance between the visual reference lines)
$maxVal = $valIndRepere * $nbDiv;
// define the distance between the visual reference lines (in milimeters)
$hRepere = floor($hDiag / $nbDiv);
// adjust the chart HEIGHT
$hDiag = $hRepere * $nbDiv;
// determine the height unit (milimiters/data unit)
$unit = $hDiag / $maxVal;
// determine the bar's thickness
$lBar = floor($lDiag / ($this->NbVal + 1));
$lDiag = $lBar * ($this->NbVal + 1);
$eColumn = floor($lBar * 80 / 100);
// draw the chart border
$this->SetLineWidth(0.2);
$this->Rect($XDiag, $YDiag, $lDiag, $hDiag);
$this->SetFont('Courier', '', 10);
$this->SetFillColor($color[0],$color[1],$color[2]);
$i=0;
foreach($data as $val)
{
//Column
$yval = $YDiag + $hDiag;
$xval = $XDiag + ($i + 1) * $lBar - $eColumn/2;
$lval = floor($eColumn/(count($val)));
$j=0;
foreach($val as $v)
{
$hval = (int)($v * $unit);
$this->SetFillColor($colors[$j][0], $colors[$j][1], $colors[$j][2]);
$this->Rect($xval+($lval*$j), $yval, $lval, -$hval, 'DF');
$j++;
}
//Legend
$this->SetXY($xval, $yval + $margin);
$this->Cell($lval, 5, $this->legends[$i],0,0,'C');
$i++;
}
//Scales
for ($i = 0; $i <= $nbDiv; $i++)
{
$ypos = $YDiag + $hRepere * $i;
$this->Line($XDiag, $ypos, $XDiag + $lDiag, $ypos);
$val = ($nbDiv - $i) * $valIndRepere;
$ypos = $YDiag + $hRepere * $i;
$xpos = $XDiag - $margin - $this->GetStringWidth($val);
$this->Text($xpos, $ypos, $val);
}
}
function SetLegends($data, $format)
{
$this->legends=array();
$this->wLegend=0;
$this->NbVal=count($data);
}
}
$pdf = new PDF_Diag();
$pdf->AddPage();
$data[0] = array(470, 490, 90);
$data[1] = array(450, 530, 110);
$data[2] = array(420, 580, 100);
// Column chart
$pdf->SetFont('Arial', 'BIU', 12);
$pdf->Cell(210, 5, 'Chart Title', 0, 1, 'C');
$pdf->Ln(8);
$valX = $pdf->GetX();
$valY = $pdf->GetY();
$pdf->ColumnChart(110, 100, $data, null, array(255,175,100));
//$pdf->SetXY($valX, $valY);
$pdf->Output();
?>

Related

Convert polygon coordinates without creating an image and testing pixel color

I need to convert a lot of x/y pixel coordinates from polygon segments (680x680) to the grid references (68x68) the polygon contains.
e.g grid references
1, 2, 3, 4
5, 6, 7, 8
9,10,11,12 etc
Performance is the ultimate goal. My working script does the job however with thousands of sets of polygon segments each minute I'm looking to improve speed further. Currently I'm using the GD library to draw a polygon, then with help of a bounding box, testing the brightness of each polygon pixel to get x/y coordinates, then finally converting those to a grid reference.
While the overhead of generating an image in memory isn't huge, there must be a better (or faster) way to do this.
Working example with output
$p = [];
$r = [];
$p['segments'] = [[144, 637], [225, 516], [85, 460], [30, 482]];
$r = segments_to_grid($p, $r);
print_r($r['grid']);
Array
(
[0] => 3133
[1] => 3134
[2] => 3135
[3] => 3136
[4] => 3137
[5] => 3138
[6] => 3199
[7] => 3200
...
...
[157] => 4092
[158] => 4093
[159] => 4094
[160] => 4095
[161] => 4161
[162] => 4162
[163] => 4229
)
Supporting functions
/**
* Convert a list of x/y coordinates to grid references
*
* #param array $p
* #param array $r
*
* #return array augmented $r
*/
function segments_to_grid($p, $r) {
$p['segments'] = isset($p['segments']) ? $p['segments'] : [];
// e.g, [[144,637],[225,516],[85,460],[30,482]]
// Return array
$r['grid'] = [];
// Define base dimensions
$w = 680;
$h = 680;
$poly_coords = [];
$min_x = $min_y = 680;
$max_x = $max_y = 0;
// Build an imagefilledpolygon compatible array and extract minimum and maximum for bounding box
foreach ($p['segments'] as $segment) {
$poly_coords[] = $segment[0];
$poly_coords[] = $segment[1];
$min_x = min($min_x, $segment[0]);
$min_y = min($min_y, $segment[1]);
$max_x = max($max_x, $segment[0]);
$max_y = max($max_y, $segment[1]);
}
// check we have something useful
if (!empty($poly_coords)) {
$r['code'] = 40;
// create image
$img = imagecreatetruecolor($w, $h);
// allocate colors (white background, black polygon)
$bg = imagecolorallocate($img, 255, 255, 255);
$black = imagecolorallocate($img, 0, 0, 0);
// fill the background
imagefilledrectangle($img, 0, 0, $w, $h, $bg);
// draw a polygon
if (imagefilledpolygon($img, $poly_coords, count($p['segments']), $black)) {
$r['code'] = 0;
// loop through the image and find the points that are black
for ($y = $min_y; $y < $max_y; $y = $y + 10) {
for ($x = $min_x; $x < $max_x; $x = $x + 10) {
$rgb = imagecolorat($img, $x, $y);
if (intval($rgb) < 16777215) {
$r['grid'][] = xy6802g68($x, $y);
}
}
}
} else {
$r['error'] = 'poly fail';
$r['code'] = 10;
}
imagedestroy($img);
} else {
$r['error'] = 'no coordinates';
$r['code'] = 20;
}
return ($r);
}
/**
* Converts X/Y 680x680 to 68x68 grid reference number.
*
* #param int $cX pixel x positon
* #param int $cY pixel y positon
* #return int grid reference number
*/
function xy6802g68($cX, $cY) {
$calcX = ceil($cX / 10) - 1;
$calcY = ceil($cY / 10) - 1;
$grid68 = $calcX + ($calcY * 68);
return ($grid68);
}
Solution thanks to #Oliver's comments.
Porting inpoly to PHP was 168 times faster than using GD image lib.
function segments_to_grid2($p, $r) {
// Define base dimensions
$vertx = $verty = [];
$min_x = $min_y = 680;
$max_x = $max_y = 0;
foreach ($p['segments'] as $segment) {
$vertx[] = $segment[0];
$verty[] = $segment[1];
$min_x = min($min_x, $segment[0]);
$min_y = min($min_y, $segment[1]);
$max_x = max($max_x, $segment[0]);
$max_y = max($max_y, $segment[1]);
}
if (!empty($vertx)) {
$nvert = count($vertx);
for ($y = $min_y; $y < $max_y; $y = $y + 10) {
for ($x = $min_x; $x < $max_x; $x = $x + 10) {
if (inpoly($nvert, $vertx, $verty, $x, $y)) {
$r['grid'][] = xy6802g68($x, $y);
}
}
}
}
return $r;
}
function inpoly($nvert, $vertx, $verty, $testx, $testy) {
$i = $j = $c = 0;
for ($i = 0, $j = $nvert - 1; $i < $nvert; $j = $i++) {
if ((($verty[$i] > $testy) != ($verty[$j] > $testy)) && ($testx < ($vertx[$j] - $vertx[$i]) * ($testy - $verty[$i]) / ($verty[$j] - $verty[$i]) + $vertx[$i])) {
$c = !$c;
}
}
return $c;
}
Running this 100 times
GD library version:
[segments_to_grid] => 0.0027089119 seconds
inpoly version
[segments_to_grid2] => 0.0001449585 seconds
168 times faster and equivalent output
Thanks Oliver!
Testing if a point lies inside a polygon is a well-known problem.
The classical solution is an algorithm called "ray casting":
Start from the point
Cast a ray in some arbitrary direction and count the number of times it crosses a polygon segment
The parity of the number gives the result (odd: inside; even: outside)
A C implementation of the algorithm is given here:
int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy)
{
int i, j, c = 0;
for (i = 0, j = nvert-1; i < nvert; j = i++) {
if ( ((verty[i]>testy) != (verty[j]>testy)) &&
(testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
c = !c;
}
return c;
}
A possible PHP version is:
function pnpoly($nvert, $vertx, $verty, $testx, $testy) {
$c = false;
for ($i = 0, $j = $nvert - 1; $i < $nvert; $j = $i++) {
if ((($verty[$i] > $testy) != ($verty[$j] > $testy))
&& ($testx < ($vertx[$j] - $vertx[$i]) * ($testy - $verty[$i]) / ($verty[$j] - $verty[$i]) + $vertx[$i])) {
$c = !$c;
}
}
return $c;
}

PHP GD creating graph from a MYSQL table

I am currently creating a bar graph for PHP using GD (inb4 use Java-script, I can't. This is a requirement) As it stands I have a functioning Bar graph in GD that i can display normal numbers with but now I am trying to retrieve data from a MYSQL table and display it in the graph.
require "connect.php";
$sql = "SELECT
title, searchcount
FROM movies
ORDER BY
searchcount DESC
LIMIT 10";
$result = $conn->query($sql);
if (mysqli_num_rows($result) > 0)
{
while($row = mysqli_fetch_array($result))
{
$data = [
$row['title'] => substr_count($row['searchcount'], "1"),
];
}
}
This above code currently works in retrieving one of the required 10 results. What I can't figure out is how to retrieve the other 9, any help would be greatly appreciated.
https://imgur.com/m8sU8QG
This here is a link to the Graph at its current state
How the graph is drawn.
/*
* Chart settings and create image
*/
// Image dimensions
$imageWidth = 700;
$imageHeight = 400;
// Grid dimensions and placement within image
$gridTop = 40;
$gridLeft = 50;
$gridBottom = 340;
$gridRight = 650;
$gridHeight = $gridBottom - $gridTop;
$gridWidth = $gridRight - $gridLeft;
// Bar and line width
$lineWidth = 1;
$barWidth = 20;
// Font settings
$font = 'OpenSans-Regular.ttf';
$fontSize = 10;
// Margin between label and axis
$labelMargin = 8;
// Max value on y-axis
$yMaxValue = 25;
// Distance between grid lines on y-axis
$yLabelSpan = 5;
// Init image
$chart = imagecreate($imageWidth, $imageHeight);
// Setup colors
$backgroundColor = imagecolorallocate($chart, 255, 255, 255);
$axisColor = imagecolorallocate($chart, 85, 85, 85);
$labelColor = $axisColor;
$gridColor = imagecolorallocate($chart, 212, 212, 212);
$barColor = imagecolorallocate($chart, 47, 133, 217);
imagefill($chart, 0, 0, $backgroundColor);
imagesetthickness($chart, $lineWidth);
/*
* Print grid lines bottom up
*/
for($i = 0; $i <= $yMaxValue; $i += $yLabelSpan) {
$y = $gridBottom - $i * $gridHeight / $yMaxValue;
// draw the line
imageline($chart, $gridLeft, $y, $gridRight, $y, $gridColor);
// draw right aligned label
$labelBox = imagettfbbox($fontSize, 0, $font, strval($i));
$labelWidth = $labelBox[4] - $labelBox[0];
$labelX = $gridLeft - $labelWidth - $labelMargin;
$labelY = $y + $fontSize / 2;
imagettftext($chart, $fontSize, 0, $labelX, $labelY, $labelColor, $font, strval($i));
}
/*
* Draw x- and y-axis
*/
imageline($chart, $gridLeft, $gridTop, $gridLeft, $gridBottom, $axisColor);
imageline($chart, $gridLeft, $gridBottom, $gridRight, $gridBottom, $axisColor);
/*
* Draw the bars with labels
*/
$barSpacing = $gridWidth / count($data);
$itemX = $gridLeft + $barSpacing / 2;
foreach($data as $key => $value) {
// Draw the bar
$x1 = $itemX - $barWidth / 2;
$y1 = $gridBottom - $value / $yMaxValue * $gridHeight;
$x2 = $itemX + $barWidth / 2;
$y2 = $gridBottom - 1;
imagefilledrectangle($chart, $x1, $y1, $x2, $y2, $barColor);
// Draw the label
$labelBox = imagettfbbox($fontSize, 0, $font, $key);
$labelWidth = $labelBox[4] - $labelBox[0];
$labelX = $itemX - $labelWidth / 2;
$labelY = $gridBottom + $labelMargin + $fontSize;
imagettftext($chart, $fontSize, 0, $labelX, $labelY, $labelColor, $font, $key);
$itemX += $barSpacing;
}
/*
* Output image to browser
*/
imagepng($chart, 'chart.png');
Ok, i tested your code and it works, so my guess is that is not formatted the right way or you not getting the right output from DB.
Try to change this lines:
$data = [
$row['title'] => substr_count($row['searchcount'], "1"),
];
to this:
$data[$row['title']] = substr_count($row['searchcount'], "1");
As you expecting to get a $key => $value pair, so the $value to not be an array(). Here are the test data i used and result:
<?php
$data = array(
'test1' => 10,
'test2' => 4,
'test3' => 10,
'test4' => 4,
'test5' => 10,
'test6' => 4,
'test7' => 10,
'test8' => 4,
'test9' => 10,
'test10' => 4
);
/*
* Chart settings and create image
*/
// Image dimensions
$imageWidth = 700;
$imageHeight = 400;
// Grid dimensions and placement within image
$gridTop = 40;
$gridLeft = 50;
$gridBottom = 340;
$gridRight = 650;
$gridHeight = $gridBottom - $gridTop;
$gridWidth = $gridRight - $gridLeft;
// Bar and line width
$lineWidth = 1;
$barWidth = 20;
// Font settings
$font = getcwd() . '/arial.ttf'; // I switched to use Arial font
$fontSize = 10;
// Margin between label and axis
$labelMargin = 8;
// Max value on y-axis
$yMaxValue = 25;
// Distance between grid lines on y-axis
$yLabelSpan = 5;
// Init image
$chart = imagecreate($imageWidth, $imageHeight);
// Setup colors
$backgroundColor = imagecolorallocate($chart, 255, 255, 255);
$axisColor = imagecolorallocate($chart, 85, 85, 85);
$labelColor = $axisColor;
$gridColor = imagecolorallocate($chart, 212, 212, 212);
$barColor = imagecolorallocate($chart, 47, 133, 217);
imagefill($chart, 0, 0, $backgroundColor);
imagesetthickness($chart, $lineWidth);
/*
* Print grid lines bottom up
*/
for($i = 0; $i <= $yMaxValue; $i += $yLabelSpan) {
$y = $gridBottom - $i * $gridHeight / $yMaxValue;
// draw the line
imageline($chart, $gridLeft, $y, $gridRight, $y, $gridColor);
// draw right aligned label
$labelBox = imagettfbbox($fontSize, 0, $font, strval($i));
$labelWidth = $labelBox[4] - $labelBox[0];
$labelX = $gridLeft - $labelWidth - $labelMargin;
$labelY = $y + $fontSize / 2;
imagettftext($chart, $fontSize, 0, $labelX, $labelY, $labelColor, $font, strval($i));
}
/*
* Draw x- and y-axis
*/
imageline($chart, $gridLeft, $gridTop, $gridLeft, $gridBottom, $axisColor);
imageline($chart, $gridLeft, $gridBottom, $gridRight, $gridBottom, $axisColor);
/*
* Draw the bars with labels
*/
$barSpacing = $gridWidth / count($data);
$itemX = $gridLeft + $barSpacing / 2;
foreach($data as $key => $value) {
// Draw the bar
$x1 = $itemX - $barWidth / 2;
$y1 = $gridBottom - $value / $yMaxValue * $gridHeight;
$x2 = $itemX + $barWidth / 2;
$y2 = $gridBottom - 1;
imagefilledrectangle($chart, $x1, $y1, $x2, $y2, $barColor);
// Draw the label
$labelBox = imagettfbbox($fontSize, 0, $font, $key);
$labelWidth = $labelBox[4] - $labelBox[0];
$labelX = $itemX - $labelWidth / 2;
$labelY = $gridBottom + $labelMargin + $fontSize;
imagettftext($chart, $fontSize, 0, $labelX, $labelY, $labelColor, $font, $key);
$itemX += $barSpacing;
}
/*
* Output image to browser
*/
imagepng($chart, 'chart.png');
?>
Output

How to draw flat topped hex (title) image in PHP?

I am trying to draw a hex using PHP. I have been trying to follow the manual from http://www.redblobgames.com/grids/hexagons/ supporting myself with leland hex generation class (https://github.com/pushcx/leland/blob/master/class_hex_image.php).
Unluckily my "hex" looks like this:
Can you please advice what am I doing wrong or tell me how can I create a proper hex?
It seems to me that function which grabs hex corners does not work correctly:
function hex_corners($center, $size)
{
$points = array();
for($i=0; $i <= 5; $i++)
{
$deg = 60 * $i; // Oblicz kąt, w którym znajduje sie róg hexu
$rad = pi() / 180 * $deg; // Przelicz kąt na radiany
$points[$i] = array_push($points, $center['x'] + $this->size / 2 * cos($rad), $center['y'] + $this->size / 2 * sin($rad));
}
return($points);
}
but I have tried to mimic the one described in http://www.redblobgames.com/grids/hexagons/ manual:
function hex_corner(center, size, i):
var angle_deg = 60 * i
var angle_rad = PI / 180 * angle_deg
return Point(center.x + size * cos(angle_rad),
center.y + size * sin(angle_rad))
I am using the following draw function:
public function hex_draw($x, $y)
{
$this->hex = imagecreatetruecolor ($this->size , $this->size);
$center['x'] = $this->size / 2;
$center['y'] = $this->size / 2;
$blue = imagecolorallocate($this->hex, 0, 0, 255);
// Get points
$points = $this->hex_corners($center, $this->size);
//die($print_r($points);
imagefilledpolygon($this->hex, $points, count($points)/2, $blue);
// flush image
header('Content-type: image/png');
imagepng($this->hex);
imagedestroy($this->hex);
}
My error was trivial.
In line
$points[$i] = array_push($points, $center['x'] + $this->size / 2 * cos($rad), $center['y'] + $this->size / 2 * sin($rad));
there was unneeded [$i] which caused problems.
Hex may be properly generated using the following code:
<?php
class hexmapimage
{
private $size = 100;
private $hex;
public function hex_draw($x, $y)
{
$this->hex = imagecreatetruecolor ($this->size , $this->size);
$center['x'] = $this->size / 2;
$center['y'] = $this->size / 2;
$black = imagecolorallocate($this->hex, 0, 0, 0);
$blue = imagecolorallocate($this->hex, 0, 0, 255);
$transparent = imagecolortransparent ($this->hex, $black);
// Get points
$points = $this->hex_corners($center, $this->size);
imagefilledrectangle($this->hex, 0, 0, $this->size, $this->size, $transparent);
imagefilledpolygon($this->hex, $points, count($points)/2, $blue);
// flush image
header('Content-type: image/png');
imagepng($this->hex);
imagedestroy($this->hex);
}
/*
* $center = array(x coordinate of center of hex, y coordinate of center of hex)
* $size = int (size of hex)
*/
function hex_corners($center, $size)
{
$points = array();
for($i=0; $i <= 5; $i++)
{
$deg = 60 * $i; // Oblicz kąt, w którym znajduje sie róg hexu
$rad = deg2rad($deg); // Przelicz kąt na radiany
array_push($points, $center['x'] + $this->size / 2 * cos($rad), $center['y'] + $this->size / 2 * sin($rad));
}
return($points);
}
}
$hex = new hexmapimage();
$hex->hex_draw(0, 0);

How to create Double bar diagram using fpdf php?

I'm using FPDF in my php project. I would like to have PDF version Double bar diagram like above image in my project. There's a way that FPDF can create Pie chart and Bar diagram in http://www.fpdf.org/en/script/script28.php. But it's not double bar diagram like what I want to get. Anyone have an idea how to create Double bar diagram using FPDF in PHP?
Many Thanks !!!
Probably you mean "COLUMN CHARTS"
It seems that there is no method to create column charts, so I tried to adapt the existing bar chart. Unfortunately I have no time to develop it further.
try this (making the necessary changes):
<?php
require('diag/sector.php');
class PDF_Diag extends PDF_Sector {
var $legends;
var $wLegend;
var $sum;
var $NbVal;
function ColumnChart($w, $h, $data, $format, $color=null, $maxVal=0, $nbDiv=4)
{
// RGB for color 0
$colors[0][0] = 155;
$colors[0][1] = 75;
$colors[0][2] = 155;
// RGB for color 1
$colors[1][0] = 0;
$colors[1][1] = 155;
$colors[1][2] = 0;
// RGB for color 2
$colors[2][0] = 75;
$colors[2][1] = 155;
$colors[2][2] = 255;
// RGB for color 3
$colors[3][0] = 75;
$colors[3][1] = 0;
$colors[3][2] = 155;
$this->SetFont('Courier', '', 10);
$this->SetLegends($data,$format);
// Starting corner (current page position where the chart has been inserted)
$XPage = $this->GetX();
$YPage = $this->GetY();
$margin = 2;
// Y position of the chart
$YDiag = $YPage + $margin;
// chart HEIGHT
$hDiag = floor($h - $margin * 2);
// X position of the chart
$XDiag = $XPage + $margin;
// chart LENGHT
$lDiag = floor($w - $margin * 3 - $this->wLegend);
if($color == null)
$color=array(155,155,155);
if ($maxVal == 0)
{
foreach($data as $val)
{
if(max($val) > $maxVal)
{
$maxVal = max($val);
}
}
}
// define the distance between the visual reference lines (the lines which cross the chart's internal area and serve as visual reference for the column's heights)
$valIndRepere = ceil($maxVal / $nbDiv);
// adjust the maximum value to be plotted (recalculate through the newly calculated distance between the visual reference lines)
$maxVal = $valIndRepere * $nbDiv;
// define the distance between the visual reference lines (in milimeters)
$hRepere = floor($hDiag / $nbDiv);
// adjust the chart HEIGHT
$hDiag = $hRepere * $nbDiv;
// determine the height unit (milimiters/data unit)
$unit = $hDiag / $maxVal;
// determine the bar's thickness
$lBar = floor($lDiag / ($this->NbVal + 1));
$lDiag = $lBar * ($this->NbVal + 1);
$eColumn = floor($lBar * 80 / 100);
// draw the chart border
$this->SetLineWidth(0.2);
$this->Rect($XDiag, $YDiag, $lDiag, $hDiag);
$this->SetFont('Courier', '', 10);
$this->SetFillColor($color[0],$color[1],$color[2]);
$i=0;
foreach($data as $val)
{
//Column
$yval = $YDiag + $hDiag;
$xval = $XDiag + ($i + 1) * $lBar - $eColumn/2;
$lval = floor($eColumn/(count($val)));
$j=0;
foreach($val as $v)
{
$hval = (int)($v * $unit);
$this->SetFillColor($colors[$j][0], $colors[$j][1], $colors[$j][2]);
$this->Rect($xval+($lval*$j), $yval, $lval, -$hval, 'DF');
$j++;
}
//Legend
$this->SetXY($xval, $yval + $margin);
$this->Cell($lval, 5, $this->legends[$i],0,0,'C');
$i++;
}
//Scales
for ($i = 0; $i <= $nbDiv; $i++)
{
$ypos = $YDiag + $hRepere * $i;
$this->Line($XDiag, $ypos, $XDiag + $lDiag, $ypos);
$val = ($nbDiv - $i) * $valIndRepere;
$ypos = $YDiag + $hRepere * $i;
$xpos = $XDiag - $margin - $this->GetStringWidth($val);
$this->Text($xpos, $ypos, $val);
}
}
function SetLegends($data, $format)
{
$this->legends=array();
$this->wLegend=0;
$this->NbVal=count($data);
}
}
$pdf = new PDF_Diag();
$pdf->AddPage();
$data[0] = array(470, 490, 90);
$data[1] = array(450, 530, 110);
$data[2] = array(420, 580, 100);
// Column chart
$pdf->SetFont('Arial', 'BIU', 12);
$pdf->Cell(210, 5, 'Chart Title', 0, 1, 'C');
$pdf->Ln(8);
$valX = $pdf->GetX();
$valY = $pdf->GetY();
$pdf->ColumnChart(110, 100, $data, null, array(255,175,100));
//$pdf->SetXY($valX, $valY);
$pdf->Output();
?>

Auto Font Size For Text (GD via PHP)

There is a space of x*y for text to go on $im (GD Image Resource) how can I choose a font size (or write text such that) it does not overflow over that area?
I think you look for the imagettfbbox function.
I used that some years ago for a script generating localized buttons for a Web interface. I actually resized buttons if the text didn't fit in the template, to keep text size consistent, but you can try to reduce the text size until the text fits.
If you are interested, I can paste some snippets of my code (or give it right away).
[EDIT] OK, here is some extracts of my code, someday I will clean it up (make it independent of target app, give samples) and make it public as a whole.
I hope the snippets make sense.
// Bounding boxes: ImageTTFBBox, ImageTTFText:
// Bottom-Left: $bb[0], $bb[1]
// Bottom-Right: $bb[2], $bb[3]
// Top-Right: $bb[4]; $bb[5]
// Top-Left: $bb[6], $bb[7]
define('GDBB_TOP', 5);
define('GDBB_LEFT', 0);
define('GDBB_BOTTOM', 1);
define('GDBB_RIGHT', 2);
#[ In class constructor ]#
// Get size in pixels, must convert to points for GD2.
// Because GD2 assumes 96 pixels per inch and we use more "standard" 72.
$this->textSize *= 72/96;
$this->ComputeTextDimensions($this->textSize, FONT, $this->text);
#[ Remainder of the class (extract) ]
/**
* Compute the dimensions of the text.
*/
function ComputeTextDimensions($textSize, $fontFile, $text)
{
$this->textAreaWidth = $this->imageHSize - $this->marginL - $this->marginR;
$this->textAreaHeight = $this->imageVSize - $this->marginT - $this->marginB;
// Handle text on several lines
$this->lines = explode(NEWLINE_CHAR, $text);
$this->lineNb = count($this->lines);
if ($this->lineNb == 1)
{
$bb = ImageTTFBBox($textSize, 0, $fontFile, $text);
$this->textWidth[0] = $bb[GDBB_RIGHT] - $bb[GDBB_LEFT];
$this->maxTextWidth = $this->textWidth[0];
$this->textHeight[0] = $bb[GDBB_BOTTOM] - $bb[GDBB_TOP];
}
else
{
for ($i = 0; $i < $this->lineNb; $i++)
{
$bb = ImageTTFBBox($textSize, 0, $fontFile, $this->lines[$i]);
$this->textWidth[$i] = $bb[GDBB_RIGHT] - $bb[GDBB_LEFT];
$this->maxTextWidth = max($this->maxTextWidth, $this->textWidth[$i]);
$this->textHeight[$i] = $bb[GDBB_BOTTOM] - $bb[GDBB_TOP];
}
}
// Is the given text area width too small for asked text?
if ($this->maxTextWidth > $this->textAreaWidth)
{
// Yes! Increase button size
$this->textAreaWidth = $this->maxTextWidth;
$this->imageHSize = $this->textAreaWidth + $this->marginL + $this->marginR;
}
// Now compute the text positions given the new (?) text area width
if ($this->lineNb == 1)
{
$this->ComputeTextPosition(0, $textSize, $fontFile, $text, false);
}
else
{
for ($i = 0; $i < $this->lineNb; $i++)
{
$this->ComputeTextPosition($i, $textSize, $fontFile, $this->lines[$i], false);
}
}
}
/**
* Compute xText and yText (text position) for the given text.
*/
function ComputeTextPosition($index, $textSize, $fontFile, $text, $centerAscDesc)
{
switch ($this->textAlign)
{
case 'L':
$this->xText[$index] = $this->marginL;
break;
case 'R':
$this->xText[$index] = $this->marginL +
$this->textAreaWidth - $this->textWidth[$index];
break;
case 'C':
default:
$this->xText[$index] = $this->marginL +
($this->textAreaWidth - $this->textWidth[$index]) / 2;
break;
}
if ($centerAscDesc)
{
// Must compute the difference between baseline and bottom of BB.
// I have to use a temporary image, as ImageTTFBBox doesn't use coordinates
// providing offset from the baseline.
$tmpBaseline = 5;
// Image size isn't important here, GD2 still computes correct BB
$tmpImage = ImageCreate(5, 5);
$bbt = ImageTTFText($tmpImage, $this->textSize, 0, 0, $tmpBaseline,
$this->color, $fontFile, $text);
// Bottom to Baseline
$baselinePos = $bbt[GDBB_BOTTOM] - $tmpBaseline;
ImageDestroy($tmpImage);
$this->yText[$index] = $this->marginT + $this->textAreaHeight -
($this->textAreaHeight - $this->textHeight) / 2 - $baselinePos + 0.5;
}
else
{
// Actually, we want to center the x-height, ie. to keep the baseline at same pos.
// whatever the text is really, ie. independantly of ascenders and descenders.
// This provide better looking buttons, as they are more consistent.
$bbt = ImageTTFBBox($textSize, 0, $fontFile, "moxun");
$tmpHeight = $bbt[GDBB_BOTTOM] - $bbt[GDBB_TOP];
$this->yText[$index] = $this->marginT + $this->textAreaHeight -
($this->textAreaHeight - $tmpHeight) / 2 + 0.5;
}
}
/**
* Add the text to the button.
*/
function DrawText()
{
for ($i = 0; $i < $this->lineNb; $i++)
{
// Increase slightly line height
$yText = $this->yText[$i] + $this->textHeight[$i] * 1.1 *
($i - ($this->lineNb - 1) / 2);
ImageTTFText($this->image, $this->textSize, 0,
$this->xText[$i], $yText, $this->color, FONT, $this->lines[$i]);
}
}
I've taken the class by PhiLho above and expanded it to dynamically scale individual lines of text to fit within the bounding box. Also I wrapped it with a call so you can see how it functions. This is pretty much copy-pasta, just change your variables and it should work out of the box.
<?
header("Content-type: image/png");
define('GDBB_TOP', 5);
define('GDBB_LEFT', 0);
define('GDBB_BOTTOM', 1);
define('GDBB_RIGHT', 2);
class DrawFont {
function DrawFont($details) {
// Get size in pixels, must convert to points for GD2.
// Because GD2 assumes 96 pixels per inch and we use more "standard" 72.
$this->textSizeMax = $details['size'];
$this->font = $details['font'];
$this->text = $details['text'];
$this->image = $details['image'];
$this->color = $details['color'];
$this->shadowColor = $details['shadowColor'];
$this->textAlign = "C";
$this->imageHSize = $details['imageHSize'];
$this->imageVSize = $details['imageVSize'];
$this->marginL = $details['marginL'];
$this->marginR = $details['marginR'];
$this->marginT = $details['marginT'];
$this->marginB = $details['marginB'];
$this->ComputeTextDimensions($this->font, $this->text);
}
/**
* Compute the dimensions of the text.
*/
function ComputeTextDimensions($fontFile, $text)
{
$this->textAreaWidth = $this->imageHSize - $this->marginL - $this->marginR;
$this->textAreaHeight = $this->imageVSize - $this->marginT - $this->marginB;
// Handle text on several lines
$this->lines = explode(' ', $text);
$this->lineNb = count($this->lines);
if ($this->lineNb == 1)
{
$this->textSize[0] = $this->textSizeMax;
$bb = ImageTTFBBox($this->textSize[0], 0, $fontFile, $text);
$this->textWidth[0] = $bb[GDBB_RIGHT] - $bb[GDBB_LEFT];
$this->maxTextWidth = $this->textWidth[0];
$this->textHeight[0] = $bb[GDBB_BOTTOM] - $bb[GDBB_TOP];
$this->textSize[0] = $this->textSizeMax;
while ($this->textWidth[0] > $this->textAreaWidth && $this->textSize[0] > 1) {
$this->textSize[0]--;
$bb = ImageTTFBBox($this->textWidth[$i], 0, $fontFile, $text);
$this->textWidth[0] = $bb[GDBB_RIGHT] - $bb[GDBB_LEFT];
$this->maxTextWidth = $this->textWidth[0];
$this->textHeight[0] = $bb[GDBB_BOTTOM] - $bb[GDBB_TOP];
}
}
else
{
for ($i = 0; $i < $this->lineNb; $i++)
{
$this->textSize[$i] = $this->textSizeMax;
$bb = ImageTTFBBox($this->textSize[$i], 0, $fontFile, $this->lines[$i]);
$this->textWidth[$i] = $bb[GDBB_RIGHT] - $bb[GDBB_LEFT];
$this->maxTextWidth = max($this->maxTextWidth, $this->textWidth[$i]);
$this->textHeight[$i] = $bb[GDBB_BOTTOM] - $bb[GDBB_TOP];
while ($this->textWidth[$i] > $this->textAreaWidth && $this->textSize[$i] > 1) {
$this->textSize[$i]--;
$bb = ImageTTFBBox($this->textSize[$i], 0, $fontFile, $this->lines[$i]);
$this->textWidth[$i] = $bb[GDBB_RIGHT] - $bb[GDBB_LEFT];
$this->maxTextWidth = max($this->maxTextWidth, $this->textWidth[$i]);
$this->textHeight[$i] = $bb[GDBB_BOTTOM] - $bb[GDBB_TOP];
}
}
}
/*
// Is the given text area width too small for asked text?
if ($this->maxTextWidth > $this->textAreaWidth)
{
// Yes! Increase button size
$this->textAreaWidth = $this->maxTextWidth;
$this->imageHSize = $this->textAreaWidth + $this->marginL + $this->marginR;
}
*/
// Now compute the text positions given the new (?) text area width
if ($this->lineNb == 1)
{
$this->ComputeTextPosition(0, $this->textSize[0], $fontFile, $text, false);
}
else
{
for ($i = 0; $i < $this->lineNb; $i++)
{
$this->ComputeTextPosition($i, $this->textSize[$i], $fontFile, $this->lines[$i], false);
}
}
}
/**
* Compute xText and yText (text position) for the given text.
*/
function ComputeTextPosition($index, $textSize, $fontFile, $text, $centerAscDesc)
{
switch ($this->textAlign)
{
case 'L':
$this->xText[$index] = $this->marginL;
break;
case 'R':
$this->xText[$index] = $this->marginL +
$this->textAreaWidth - $this->textWidth[$index];
break;
case 'C':
default:
$this->xText[$index] = $this->marginL +
($this->textAreaWidth - $this->textWidth[$index]) / 2;
break;
}
if ($centerAscDesc)
{
// Must compute the difference between baseline and bottom of BB.
// I have to use a temporary image, as ImageTTFBBox doesn't use coordinates
// providing offset from the baseline.
$tmpBaseline = 5;
// Image size isn't important here, GD2 still computes correct BB
$tmpImage = ImageCreate(5, 5);
$bbt = ImageTTFText($tmpImage, $this->textSizeMax, 0, 0, $tmpBaseline,
$this->color, $fontFile, $text);
// Bottom to Baseline
$baselinePos = $bbt[GDBB_BOTTOM] - $tmpBaseline;
ImageDestroy($tmpImage);
$this->yText[$index] = $this->marginT + $this->textAreaHeight -
($this->textAreaHeight - $this->textHeight) / 2 - $baselinePos + 0.5;
}
else
{
// Actually, we want to center the x-height, ie. to keep the baseline at same pos.
// whatever the text is really, ie. independantly of ascenders and descenders.
// This provide better looking buttons, as they are more consistent.
$bbt = ImageTTFBBox($textSize, 0, $fontFile, "moxun");
$tmpHeight = $bbt[GDBB_BOTTOM] - $bbt[GDBB_TOP];
$this->yText[$index] = $this->marginT + $this->textAreaHeight -
($this->textAreaHeight - $tmpHeight) / 2 + 0.5;
}
}
/**
* Add the text to the button.
*/
function DrawText()
{
$this->maxTextHeight = 0;
// find maxTextHeight
for ($i = 0; $i < $this->lineNb; $i++)
{
if ($this->textHeight[$i] > $this->maxTextHeight) {
$this->maxTextHeight = $this->textHeight[$i];
}
}
for ($i = 0; $i < $this->lineNb; $i++)
{
// Increase slightly line height
$yText = $this->yText[$i] + $this->maxTextHeight * 1.1 *
($i - ($this->lineNb - 1) / 2);
ImageTTFText($this->image, $this->textSize[$i], 0,
$this->xText[$i]+2, $yText+2, $this->shadowColor, $this->font, $this->lines[$i]);
ImageTTFText($this->image, $this->textSize[$i], 0,
$this->xText[$i], $yText, $this->color, $this->font, $this->lines[$i]);
}
}
}
// Script starts here
$im = imagecreatefromjpeg("/home/cvgcfjpq/public_html/fb/img/page_template.jpg");
$color = imagecolorallocate($im, 235, 235, 235);
$shadowColor = imagecolorallocate($im, 90, 90, 90);
$details = array("image" => $im,
"font" => "OldSansBlack.ttf",
"text" => $_GET['name'],
"color" => $color,
"shadowColor" => $shadowColor,
"size" => 40,
"imageHSize" => 200,
"imageVSize" => 250,
"marginL" => 5,
"marginR" => 5,
"marginT" => 5,
"marginB" => 5);
$dofontobj =& new DrawFont($details);
$dofontobj->DrawText();
imagepng($im);
imagedestroy($im);
unset($px);
?>

Categories