61
Tags:
php
Skrevet af
Bruger #285
@ 08.12.2003
Indledende kommentarer
I denne artikel vil jeg beskrive, hvordan med ved hjælp af PHP og GD-Lib kan lave ”dynamiske” cirkeldiagrammer. Denne artikel kræver lidt kendskab til PHP, GD-Lib og matematik i forvejen, men hvis man gider læse dokumentationen på de links, jeg har givet, kan man sagtens lave dette uden foregående kendskab. Det kræver også, at man har lidt forstand på cirkler, trigonometri, men det ridser jeg lige op, så vi har alle med.
Man skal være opmærksom på, at der i denne artikel ikke er gjort noget i emnet inputvalidering, så man skal give scriptet den rigtige type oplysninger for det virker.
Teori om cirkler
Når man skal lave et cirkeldiagram, skal man, som navnet siger, lavet en illustrativt opdelt cirkel ved hjælp af farver. For at gøre det, laver man nogle streger fra centrum af cirklen og ud til cirkelperiferien (”selve cirklen” så at sige). Derefter farver man disse områder. Det, der rent praktisk er lettest at gøre, er at først ”tegne” et stykke ved hjælp af streger og herefter fylde det med farve, og derefter næste osv.
Hvis man kigger på illustrationen nedenfor kan man se nogle betegnelser. Hvis man bruger nogle simple trigonometriske regler, kan man finde punktet (x,y) som er det punkt i cirkelperiferien, som den linie fra centrum skal gå ud til ud fra den angivne antal grader. Jeg forklarer ikke trigonometrien, da det godt kan være svært at forstå, hvis man endnu ikke har lært det.
Model:
Vinkler:Punkter:Indledende udregninger
Den måde, man finder ud af, hvor store ”cirkelstykkerne” skal være er, at man tager summen af inputtallene. Dette gøres med php-funktionen array_sum (URL:
http://php.net/array_sum ). Da vi ved, at der er 360° i en cirkel bruges denne oplysning til at finde forholdet mellem antallet af grader samt summen af input. Det forhold kalder vi ”degreesPrUnit”, og er givet ved:
$sumOfNumbers = array_sum($numbers);
$degreesPrUnit = 360 / $sumOfNumbers;
Dette tal skal vi senere bruge, når vi skal finde de tilhørende x- og y-værdier. Vi skal også indledende sætte nogle indstillinger for scriptet – så som fx en margin, hvor mange pixels en forklaringslinie skal fylde (en farveboks samt en forklaringstekst (kommer senere)). Jeg har kaldt dem følgende, og har givet dem følgende værdier, og sat sammen med overstående giver det følgende:
reset($numbers);
reset($names);
$countNumbers = count($numbers);
$countNames = count($names);
$settingSizePrNameDescription = 15;
$settingMargin = 5;
$width = $settingMargin + (2 * $radius) + $settingMargin;
$height = $settingMargin + (2 * $radius) + $settingMargin + ($settingSizePrNameDescription * $countNumbers) + $settingMargin;
$centerCircle = round($width / 2);
$sumOfNumbers = array_sum($numbers);
$degreesPrUnit = 360 / $sumOfNumbers;
$numbersDegrees;
$namesOnSlices;
Først sættes pointeren til starten i array’erne med funktionen reset (URL:
http://php.net/reset ). Derefter sætter jeg, at der skal 15 px til hver række forklaring. Jeg sætter ydermere margin til 5 px. Nu har jeg nok oplysninger til at regne bredden og højden ud. Bredden er margin i venstre side + cirklens bredde (2*r) + margin i højre side. Højden på billedet skal være det samme som bredden plus størrelsen på hver forklaring ganget med antallet af forklaringer (antal numre, der er i input). De sidste to variabelinitialiseringer er blot for at sætte dem, så vi ved, at de senere bliver brugt.
Nu oprettes billedet med de størrelser, vi har regnet ud, med funktionen imagecreatetruecolor (URL:
http://php.net/imagecreatetruecolor ). Jeg initialiserer også nogle farver (en til baggrund, en til at tegne cirklen samt andre småting og til sidst 10 til forskellige cirkelstykker – hvis man skal have flere tal ind i cirkeldiagrammet, initialiserer man blot flere på samme måde) med funktionen imagecolorallocate (URL:
http://php.net/imagecolorallocate ). Derefter tegner jeg en hvid baggrund med funktionen imagefilledrectangle (URL:
http://php.net/imagefilledrectangle ). Til sidst tegner jeg selve cirklen med funktionen imageellipse (URL:
http://php.net/imageellipse ).
// billedet oprettes
$img = imagecreatetruecolor($width, $height);
// farver initialiseres
$colorsWhite = imagecolorallocate($img, 0xFF, 0xFF, 0xFF);
$colorsBlack = imagecolorallocate($img, 0x00, 0x00, 0x00);
$colorsCircle[1] = imagecolorallocate($img, 0xFF, 0x00, 0x00);
$colorsCircle[2] = imagecolorallocate($img, 0x00, 0xFF, 0x00);
$colorsCircle[3] = imagecolorallocate($img, 0x00, 0x00, 0xFF);
$colorsCircle[4] = imagecolorallocate($img, 0xFF, 0xFF, 0x00);
$colorsCircle[5] = imagecolorallocate($img, 0xFF, 0x00, 0xFF);
$colorsCircle[6] = imagecolorallocate($img, 0x00, 0xFF, 0xFF);
$colorsCircle[7] = imagecolorallocate($img, 0xEE, 0xEE, 0xEE);
$colorsCircle[8] = imagecolorallocate($img, 0xCC, 0xCC, 0xCC);
$colorsCircle[9] = imagecolorallocate($img, 0x99, 0x99, 0x99);
$colorsCircle[10] = imagecolorallocate($img, 0x66, 0x66, 0x66);
// baggrundsfarve
imagefilledrectangle($img, 0, 0, $width, $height, $colorsWhite);
// selve cirklen tegnes
imageellipse($img, $centerCircle, $centerCircle, 2 * $radius, 2 * $radius, $colorsBlack);
Herefter løber jeg alle inputtallene igennem og ganger med det forhold vi før fandt ud af, degreesPrUnit, og lagrer i et array, numbersDegrees. Samtidig regner jeg ud, hvor mange procent tallet (der bruger jeg funktionen number_format til at gøre tallet pænere – URL:
http://php.net/number-format ) er i forhold til summen og laver fylder i et array, numbersTmp, der bliver brugt, sådan, at hvis der ikke er angivet noget navn til et nummer, så bliver inputtallet skrevet som en form for ”erstatning”.
foreach($numbers as $value)
{
$numbersDegrees[] = $value * $degreesPrUnit;
$numbersProcentOfSum[] = number_format(($value/$sumOfNumbers) * 100, 2, ",", ".");
$numbersTmp[] = $value;
}
foreach($names as $value)
{
$namesOnSlices[] = $value;
}
Nu begynder jeg at tegne cirkelafsnittene og farve dem. Til at tegne linier med, bliver funktionen imageline brugt (URL:
http://php.net/imageline ). Jeg laver gradeantallet om til radianer med funktionen deg2rad da funktionerne sin og cos tager antal radianer som argument (URL’s:
http://php.net/deg2rad ,
http://php.net/cos og
http://php.net/sin ). Jeg fylder afsnittene med funktionen imagefilltoborder (URL:
http://php.net/imagefilltoborder ). Jeg skriver det tilhørende navn (eller blot tallet, hvis der ikke er noget navn) samt procent i parentes bagefter med funktionen imagestring (URL:
http://php.net/imagestring ).
$indexColor = 1;
$indexDegree = 0;
foreach($numbersDegrees as $value)
{
$degree = $value + $indexDegrees;
$indexDegrees = $degree;
// koordinater til at farve cirkelafsnit beregnes i følgende afsnit kode
// konvertér fra grader til radian
$rad = deg2rad($indexDegrees);
// x-koordinaten beregnes
$x = (cos($rad) * ($radius - 5)) + $centerCircle;
// y-koordinaten beregnes
$y = (sin($rad) * ($radius - 5)) + $centerCircle;
imagefilltoborder($img, $x, $y, $colorsBlack, $colorsCircle[$indexColor + 1]);
// koordinater til at tegne stregerne til afgrænsning af cirkelafsnit beregnes i følgende afsnit kode
// konvertér fra grader til radian
$rad = deg2rad($degree);
$x = (cos($rad) * ($radius - 1)) + $centerCircle;
$y (sin($rad) * ($radius - 1)) + $centerCircle;
imageline($img, $centerCircle, $centerCircle, $x, $y, $colorsBlack);
// koordinater til at tegne den sorte firkant farven til forklaring forklaringen af data
imagerectangle($img, $settingMargin, (2 * $radius) + (2 * $settingMargin) + (($indexColor - 1) * $settingSizePrNameDescription), ($settingMargin) + 10, ((2 * $radius) + (2 * $settingMargin) + (($indexColor - 1) * $settingSizePrNameDescription)) + 10, $colorsBlack);
// forklaringsboksene skal også have lidt farve
imagefilltoborder($img, $settingMargin + 1, ((2 * $radius) + (2 * $settingMargin) + (($indexColor - 1) * $settingSizePrNameDescription)) + 1, $colorsBlack, $colorsCircle[$indexColor + 1]);
// hvis der ikke er noget tilhørende navn
if ($namesOnSlices[$indexColor - 1] == "")
$namesOnSlices[$indexColor - 1] = $numbersTmp[$indexColor - 1];
// info skrives
imagestring($img, 2, 20, (2 * $radius) + (2 * $settingMargin) + (($indexColor - 1) * $settingSizePrNameDescription), $namesOnSlices[$indexColor - 1] . " (" . $numbersProcentOfSum[$indexColor - 1] . "%)", $colorsBlack);
// næste!
$indexColor++;
}
// farver sidste slice – samme princip som alle de andre
$rad = deg2rad(1);
$x = (cos($rad) * ($radius - 5)) + $centerCircle;
$y = (sin($rad) * ($radius - 5)) + $centerCircle;
imagefilltoborder($img, $x, $y, $colorsBlack, $colorsCircle[2]);
// billedet tegnes
imagepng($img);
imagedestroy($img);
Optimering af koden
Man kan sagtens optimere og arbejde med koden, men meget af det kræver, at man laver nogle skarpere regler for input – det optimale ville være at lave en funktion til at validere input, og så optimere funktionerne endnu mere. Dette vil jeg ikke beskrive i denne artikel, da denne blot er for at give en idé om, hvordan det
kunne laves.
Samlet kode
function cirkeldiagram($numbers, $names, $radius)
{
reset($numbers);
reset($names);
$countNumbers = count($numbers);
$countNames = count($names);
$settingSizePrNameDescription = 15;
$settingMargin = 5;
$width = $settingMargin + (2 * $radius) + $settingMargin;
$height = $settingMargin + (2 * $radius) + $settingMargin + ($settingSizePrNameDescription * $countNumbers) + $settingMargin;
$centerCircle = round($width / 2);
$sumOfNumbers = array_sum($numbers);
$degreesPrUnit = 360 / $sumOfNumbers;
$numbersDegrees;
$namesOnSlices;
// billedet oprettes
$img = imagecreatetruecolor($width, $height);
// farver initialiseres
$colorsWhite = imagecolorallocate($img, 0xFF, 0xFF, 0xFF);
$colorsBlack = imagecolorallocate($img, 0x00, 0x00, 0x00);
$colorsCircle[1] = imagecolorallocate($img, 0xFF, 0x00, 0x00);
$colorsCircle[2] = imagecolorallocate($img, 0x00, 0xFF, 0x00);
$colorsCircle[3] = imagecolorallocate($img, 0x00, 0x00, 0xFF);
$colorsCircle[4] = imagecolorallocate($img, 0xFF, 0xFF, 0x00);
$colorsCircle[5] = imagecolorallocate($img, 0xFF, 0x00, 0xFF);
$colorsCircle[6] = imagecolorallocate($img, 0x00, 0xFF, 0xFF);
$colorsCircle[7] = imagecolorallocate($img, 0xEE, 0xEE, 0xEE);
$colorsCircle[8] = imagecolorallocate($img, 0xCC, 0xCC, 0xCC);
$colorsCircle[9] = imagecolorallocate($img, 0x99, 0x99, 0x99);
$colorsCircle[10] = imagecolorallocate($img, 0x66, 0x66, 0x66);
// baggrundsfarve
imagefilledrectangle($img, 0, 0, $width, $height, $colorsWhite);
// selve cirklen tegnes
imageellipse($img, $centerCircle, $centerCircle, 2 * $radius, 2 * $radius, $colorsBlack);
foreach($numbers as $value)
{
$numbersDegrees[] = $value * $degreesPrUnit;
$numbersProcentOfSum[] = number_format(($value/$sumOfNumbers) * 100, 2, ",", ".");
$numbersTmp[] = $value;
}
foreach($names as $value)
{
$namesOnSlices[] = $value;
}
$indexColor = 1;
$indexDegree = 0;
foreach($numbersDegrees as $value)
{
$degree = $value + $indexDegrees;
$indexDegrees = $degree;
// koordinater til at farve cirkelafsnit beregnes i følgende afsnit kode
// konvertér fra grader til radian
$rad = deg2rad($indexDegrees);
// x-koordinaten beregnes
$x = (cos($rad) * ($radius - 5)) + $centerCircle;
// y-koordinaten beregnes
$y = (sin($rad) * ($radius - 5)) + $centerCircle;
imagefilltoborder($img, $x, $y, $colorsBlack, $colorsCircle[$indexColor + 1]);
// koordinater til at tegne stregerne til afgrænsning af cirkelafsnit beregnes i følgende afsnit kode
// konvertér fra grader til radian
$rad = deg2rad($degree);
$x = (cos($rad) * ($radius - 1)) + $centerCircle;
$y (sin($rad) * ($radius - 1)) + $centerCircle;
imageline($img, $centerCircle, $centerCircle, $x, $y, $colorsBlack);
// koordinater til at tegne den sorte firkant farven til forklaring forklaringen af data
imagerectangle($img, $settingMargin, (2 * $radius) + (2 * $settingMargin) + (($indexColor - 1) * $settingSizePrNameDescription), ($settingMargin) + 10, ((2 * $radius) + (2 * $settingMargin) + (($indexColor - 1) * $settingSizePrNameDescription)) + 10, $colorsBlack);
// forklaringsboksene skal også have lidt farve
imagefilltoborder($img, $settingMargin + 1, ((2 * $radius) + (2 * $settingMargin) + (($indexColor - 1) * $settingSizePrNameDescription)) + 1, $colorsBlack, $colorsCircle[$indexColor + 1]);
// hvis der ikke er noget tilhørende navn
if ($namesOnSlices[$indexColor - 1] == "")
$namesOnSlices[$indexColor - 1] = $numbersTmp[$indexColor - 1];
// info skrives
imagestring($img, 2, 20, (2 * $radius) + (2 * $settingMargin) + (($indexColor - 1) * $settingSizePrNameDescription), $namesOnSlices[$indexColor - 1] . " (" . $numbersProcentOfSum[$indexColor - 1] . "%)", $colorsBlack);
// næste!
$indexColor++;
}
// farver sidste slice – samme princip som alle de andre
$rad = deg2rad(1);
$x = (cos($rad) * ($radius - 5)) + $centerCircle;
$y = (sin($rad) * ($radius - 5)) + $centerCircle;
imagefilltoborder($img, $x, $y, $colorsBlack, $colorsCircle[2]);
// billedet tegnes
imagepng($img);
imagedestroy($img);
}
// kunne kaldes på følgende måde:
$stats = explode(",", $_GET["s"]);
$navne = explode(",", $_GET["n"]);
header("Content-type: image/png");
cirkeldiagram($stats, $navne, $_GET["r"]);
Hvad kan jeg bruge det til?
Noget, dette ville være smart at bruge til er fx en afstemningsstatistik. Fx pga., at der er indbygget procentregning i, så tallene blot skal læses ud fra databasen og sættes ind i et array, scriptet så kan "fodres" med.
Det var alt!
Det var alt for denne gang. Skriv meget gerne ris/ros. Jeg har været lidt i tvivl om, hvor meget der skulle med, men dette var, hvad det endte med
Når du laver scriptet, så husk på, at der ingen inputvalidering er (betyder bl.a. at specialtegn som fx æ, ø og å ikke kan vises med imagestring - det betyder også, at hvis man vil skrive kommatal skal det gøres med engelsk komma - punktum - fx 45.6)
Et diagram lavet med overstående scriptDette er lavet med URL'en (blot for eksemplets skyld):
fil.php?r=50&s=50,45,87,54&n=Dansk,Svensk,Norsk,Finsk
Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.
Del også gerne artiklen med dine Facebook venner:
Kommentarer (21)
Flot og lærerig artikel :-)
alt for svært....men selvfølgelig også en erfaren skribent/udvikler der er forfatter !
KasperTech: Derfor behøver den jo ikke være dårlig :s (har en mistanke om, at det er dig, der har ratet lavt (2 så vidt jeg kan regne ud)?) Hvis du bruger lidt tid på at sætte dig ind i det, er jeg sikker på, at du nok skal forstå det
Ellers spørg endelig
Det ser helt sjovt ud. Det må jeg prøv tak for artiklen....
Mvh
Janus
P.s Jeg skal lave noget til en evaluering af et fag og tror denne metode kan anvendes...
Uuuuha, det her ligner guffeluf :-), den må læses ordenligt igennem. Første lille ting jeg bed mærke i som jeg ikke brød mig om var måden du lavede $colorsCircle, hvorfor skrive $colorsCircle[0], [1] osv. når man bare kan bruge []? Ellers ligner det guf, jeg vil kigge lidt nærmere på den og rate den bagefter :-)
Huha, der er vist lidt mere matematik til stede end jeg har lært, nå, what ever, det skal da ikke stå i vejen for at det er en god artikel!
Udover det jeg allerede har skrevet, så laver du dine foreach med f.eks. foreach($numbers as $value) så vidt jeg kan huske står der på www.phpnet.dk/foreach at man bør lave dem som foreach($numbers as $number), men igen mindre detajler, men når jeg nu ikke kan smide andet end det efter dig må jeg jo gøre det.
Igen rigtig rigtig god artikel! (den fik selvfølgelig 5 herfra ;-))
Tak skal du have
Jeg må vist indrømme, at nogle ting kunne have været lavet smartere, men det er småting :p
Mht. den foreach - hvad er problemer, siger du?
Rigtig god artikel, lige til at bygge videre på
Kaare: Alle eksemplerne på dk.php.net/foreach bliver skrevet med $noget as $nogetAndet, og ikke med samme navn.
Sørmer ja, mener ellers at jeg har læst det et sted
Jeg har kun en ting at sige... Kanon artikel... Fatter ikke så meget af matematiken, men pyt med det. Det virker da ihvertfald... Rigtig god artikel. Keep up the good work.
Wauv... Det var jeg også en gang med at lave, men matematikken i det blev for meget...
Rigtig god artikel... Topkarakter fra mig
Ved første øjekast ser der ud til at være meget godt i denne artikel. Og rart at du gider lave matematiken for mig
. Rater den, når jeg har testet det
Jaja... Sådan kan det skam også gøres.
Ellers en udemærket artikel. Det kunne dog laves lidt nemmere
lol.. svært matematik? noo..
Men scriptet er pænt og nydelig gennemgang!
Jesper -> Nej, men hvis man ikke har haft det, så er det svært
Man lærer som regel ikke cirklens centrumsligning i folkeskolen, så sikkert derfor.
Simon -> Så skriv gerne en artikel om det, eller blot hurtigt beskrevet her, så man kan få gode idéer
Tror godt jeg fatter artiklen (Matematikken er jeg indforstået med man går jo ikke på gym for ingenting) Men der er nok nogen der falder af da koden er så stor og det kan gøre at hvis de ikke li selv sidder og roder med noget ligende så er det svær at visualisere den i hovedet, men ellers er den fed!
Rigtig god artikel, men matematiken er svær, da jeg kun går i folkeskolen.(femte klasse)
AAAAH... Det jo alt det jeg har haft af mat iår der blir brugt der... LOL.. men fed artikel.. har set de der cirkel diagrammer flere steder og undret mig over hvordan man lavede dem...
Er rimelig ny indenfor php, men tror jeg fattede det meste bag scriptet.. tror jeg vil teste det engang.. genialt til at vise afstemninger osv med...
Gæt hvad.. Nemmerlig... Top karakter her fra mig
Smart nok, men der mangler et lighedstegn på linie 76:
$y = (sin($rad) * ($radius - 1)) + $centerCircle;
Generelt en interessant artikel. Blot ærgerligt at funktionen opfører sig mærkeligt engang imellem.
Hvis man kører funktion med følgende input, så farvelægger den lidt forkert. 2 af kagestykkerne får samme farve, hvilket de ikke bør.
-------
//Radius
$r="50";
//Tal
$s="500,300,500,100";
//Navne
$n="Signe,Julie,Ulla,Bente";
// kunne kaldes på følgende måde:
$stats = explode(",", $s);
$navne = explode(",", $n);
header("Content-type: image/png");
cirkeldiagram($stats, $navne, $r);
Sorry, det var de forkerte parametre jeg angav. Her er dem som fejler:
------------
$s="1,2,3,4,5";
//Navne
$n="Signe,Julie,Ulla,Bente,Jenni";
// kunne kaldes på følgende måde:
$stats = explode(",", $s);
$navne = explode(",", $n);
header("Content-type: image/png");
cirkeldiagram($stats, $navne, $r);
Du skal være
logget ind for at skrive en kommentar.