7. Balsavimas, tyrimai ir pyragų diagramos

Formos ir CGI programos padeda lengviau vadovauti tyrimams ir balsavimui Web'e. Pažiūrėkime į taikomąją programą, kuri atvaizduoja tyrimų rezultatus diagramų forma ir dinamiškai sukuria pyraginę diagramą atvaizduojantį tyrimų rezultatus.

Ši taikomoji programa realiai susideda iš trijų atskirų dalių:

Čia yra HTML dokumentas su forma, kurią pamatys vartotojas:

<HTML><HEAD><TITLE>Ice Cream Survey</TITLE></HEAD>
<BODY>
<H1>Favorite Ice Cream Survey</H1>
<HR>
<FORM ACTION="/cgi-bin/ice_cream.pl" METHOD="POST">
What is your favorite flavor of ice cream?
<P>
<INPUT TYPE="radio" NAME="ice_cream" VALUE="Vanilla" CHECKED>Vanilla<BR>
<INPUT TYPE="radio" NAME="ice_cream" VALUE="Strawberry">Strawberry<BR>
<INPUT TYPE="radio" NAME="ice_cream" VALUE="Chocolate">Chocolate<BR>
<INPUT TYPE="radio" NAME="ice_cream" VALUE="Other">Other<BR>
<P>
<INPUT TYPE="submit" VALUE="Submit the survey">
<INPUT TYPE="reset" VALUE="Clear your choice">
</FORM>
<HR>
If you would like to see the current results, click
<A HREF="/cgi-bin/pie.pl/ice_cream.dat">here</A>
</BODY>
</HTML>

Tai paprasta forma, kuri paklausia tik vieno klausimo. Forma parodyta paveiksle Pav 7.3

Pav 7.3 Valgomųjų ledų forma

Pav 7.3 Valgomųjų ledų forma

Atkreipkite dėmesį į papildomos informacijos naudojimą HREF atribute formos apačioje (žiūrėkite kodą aukščiau). Ši kelio informacija parodo duomenų failą šiam tyrimui, ice.cream.dat, ir bus saugoma aplinkos kintamajame PATH_INFO. Mes taip pat galime naudoti tokia užklausą:

<A HREF="/cgi-bin/pie.pl/ice_cream.dat">čia</A>.

Bet jei mes perduodame kelią, tai logiškiau būtų tą informaciją perduoti kaip papildomą kelią. Jei mes perduotume informaciją kaip užklausos eilutę, turėtume šifruoti kai kuriuos simbolius*. Pažiūrėkime į duomenų failo (ice_cream.dat) formatą:

Vanilla::Strawberry::Chocolate::Other
0::0::0::0
red::yellow::blue::green

Kaip jūs ir matote, toks skyriklis "::" atskiria kiekvieną lauką faile. Unikalūs skyrikliai turi būti naudojami, kad jie nesusimaišytų su duomenimis.

Pirmą eilutę sudaro visi pasirinkimai tyrime. Antrą eilutę sudaro tikri duomenys (pradžioje visos reikšmės turi būti nuliai). Ir paskutinę eilutę sudaro spalvos, kurios bus naudojamos piešiant diagramą. Kitais žodžiais sakant raudona bus naudojama piešti diagramos daliai atitinkančiai vanilinius ledus pyraginėje diagramoje. Spalvų skaičius yra ribotas - galimos tik tos spalvos kurios aprašytos CGI pyraginės diagramos programoje.

Formos apdorojimas

CGI programa (ice_cream.pl) dekoduoja formos informaciją, pakeičia jos formą, kad galėtų įrašyti į duomenų failą ir įrašo į duomenų failą. Programa pati savyje neturi formos.

Programa prasideda taip:

#!/usr/local/bin/perl

$webmaster = "shishir\@bu\.edu";
$document_root = "/home/staff/Virga/public_html/CGI_knyga";
$ice_cream_file = "/ice_cream.dat";
$full_path = $document_root . $ice_cream_file;

$exclusive_lock = 2;
$unlock = 8;

&parse_form_data(*poll);
$user_selection = $poll{'ice_cream'};

Formos informacija yra patalpinama į poll masyvą. parse_form_data procedūra yra ta pati, kurią naudojome anksčiau. Kadangi parse_form_data dekoduoja abu, ir GET ,ir POST informacijos pateikimus, vartotojas gali pateikti savo mėgstamiausią skonį arba su GET užklausa arba per formą. ice_cream laukas, kuris atvaizduoja vartotojo pasirinkimą, yra saugomas user_selection kintamajame.

if ( open (POLL, "<" . $full_path) ) {
flock (POLL, $exclusive_lock);

for ($loop=0; $lopp < 3; $loop++0 {
$line[$loop] = <POLL>;
$line[$loop] =~ s/\n$//;
}

Duomenų failas atidaromas realiu rėžimu ir užrakinamas. Cikle iš failo nuskaitomos pirmos trys eilutės ir išsaugomos line masyve. Eilutės pabaigos simboliai, esantys kiekvienos eilutės gale, pašalinami. Mes naudojame reguliarią išraišką, kad tai padaryti, o ne chop operatorių, kadangi trečios eilutės gale eilutės pabaigos simbolio gali ir nebūti ir chop tuomet pašalintų paskutinį eilutės simbolį ir sukeltų kitas problemas.

@options = split ("::", $line[0]);
@data    = split ("::", $line[1]);
$colors  = $line[2];

flock (POLL, $unlock); close (POLL);

Pirma failo eilute yra padalinama "::" simbolių vietose ir išsaugoma options masyve. Kiekvienas elementas šiame masyve atstoja atskirą sprendimą (ar skonį) apklausoje(tyrime). Tas pats padaryta ir su antra duomenų failo eilute. Pagrindinė to priežastis yra rasti ir padidinti vartotojo pasirinktą ledų skonį ir parašyti informaciją atgal į failą. Tačiau trečia eilutė, kurioje yra užkoduotos pyraginės diagramos spalvos nebus keičiama.

$item_no = 3;
for ($loop=0; $loop <= $#options; $loop++) { if ($options[$loop] eq $user_selection) { $item_no = $loop;
last;
}
}

Ciklas patikrina kiekvieną skonį ir palygina jį su vartotojo pasirinktu. Jei jie sutampa, tai item_no kintamojo reikšmė parodys vartotojo pasirinktą skonį masyve. Jei nebuvo rastas toks skonis kokį vartotojas pasirinko, item_no turės reikšmę 3 pagal nutylėjimą, kuri šiuo atveju atitiks "Kitas". Vienintelė priežastis dėl ko gali būti nerastas skonis, tai jei vartotojas script'ą pasiekė naudodamas GET užklausą ir pateikė skonį, kuris neįtrauktas į tyrimą.

$data[$item_no]++;

Duomenų dalis, atitinkanti pasirinktą skonį yra padidinama vienetu.

if ( open (POLL, ">" . $full_path) ) {
flock (POLL, $exclusive_lock);

Failas atidaromas ne papildymo, bet rašymo rėžimu. Taigi, visas duomenų failas bus perrašytas.

print POLL join ("::", @options), "\n";
print POLL join ("::", @data), "\n";
print POLL $colors, "\n";

Visi elementai options ir data masyvuose yra sujungiami į vieną eilutę, atskiriant juos "::" skyrikliu ir įrašomi į failą.

flock (POLL, $unlock);
close(POLL);

print "Content-type: text/html", "\n\n";

print <<End_of_Thanks;
<HTML>
<HEAD><TITLE>Thank You!</TITLE></HEAD>
<BODY>
<H1>Thank You!</H1>
<HR>
Thanks for participating in the Ice Cream survey. If you would like to see the current results, click <A HREF="/cgi-bin/pie.pl${ice_cream_file}">here</A>.
</BODY></HTML>

End_of_Thanks

Duomenų failas ura atrakinamas ir uždaromas. Parodomas padėkos pranešimas kartu su nuoroda į CGI programą, kuri parodo diagramą.

} else {
&return_error(500, "Ice Cream Poll File Error", "Cannot write to the poll data file [$full_path]."); }
} else {

&return_error (500, "Ice Cream Poll File Error", "Cannot read from the poll data file [$full_path]."); exit (0);

Jei failo nepavyksta atidaryti, klientas gauna klaidos pranešimą. Kadangi jūs jau esate susipažinę su ice_cream.pl programos naudojamomis procedūromis (return_error ir parse_form_data), tai mes jų čia ir nebenagrinėsime.

Pyraginės diagramos piešimas

pie.pl programa skaito tyrimų duomenų failą ir išveda rezultatus arba kaip pyraginę diagramą, arba kaip paprasto teksto lentelę, priklausomai nuo vartotojo naršyklės sugebėjimų. Programa gali būti pasiekta per tokią URL:

http://www.soften.ktu.lt/cw/~virga/pie.pl/ice_cream.dat

kur mes naudojame papildomą kelio informaciją, kad nurodyti ice_cream.dat, kaip duomenų failą, esantį dokumento pagrindinėje direktorijoje. Naršyklėje su galimybe atvaizduoti diagramą( tokiose kaip Netscape Navigator), diagrama atrodys kaip paveiksle 7.4

Pav 7.4 Pyraginė diagrama

Pav 7.4 Pyraginė diagrama

Programa prasideda taip:

#!/usr/local/bin/perl5

use GD;

$webmaster = "shishir\@bu\.edu";
$document_root = "/home/staff/Virga/public_html/CGI_knyga";
&read_data_file (*slices, *slices_color, *slices_message);
$no_slices = &remove_empty_slices();

GD grafinė biblioteka bus naudojama, kad sukurti pyraginę diagramą. read_data_file procedūra skaito informaciją iš duomenų failo ir padedą ja atitinkamai į slices, slices_color ir slices_message masyvus. remove_empty_slices procedūra patikrina šiuos tris masyvus, ar juose nėra nulinių reikšmių ir gražina masyvų be nulinių reikšmių skaičių no_slice kintamajame.

if ($no_slices == -1) { &no_data ();

Kai visos reikšmės duomenų faile yra nulinės, remove_empty_slices procedūra gražina reikšmę -1. Jei -1 yra gražinama į no_slices kintamąjį, tai no_data procedūra yra iškviečiama, kad išvestų pranešimą, kad nėra rezultatų šiame faile.

} else {
$nongraphic_browsers = 'Lynx|CERN-LineMode';
$client_browser = $ENV{'HTTP_USER_AGENT'};

if ($client_browser =~ /$nongraphic_browsers/) { &text_results(); } else { &draw_pie (); }
}

exit(0);

Jei vartotojo naršyklė palaiko grafikos atvaizdavimą, tai pyraginei diagramai nupiešti iškviečiama procedūra draw_pie. Kitu atveju atvaizduoti užklausos rezultatus kaip tekstą iškviečiama text_results procedūra. Tai ir visas pagrindinės procedūros kūnas. Procedūras kurios padaro visą darbą aptarsime žemiau.

Procedūra no_data atvaizduoja paprastą pranešimą apie tai, kad nėra jokios informacijos duomenų faile.

sub no_data
{print "Content-type: text/html", "\n\n";

print <<End_of_Message;
<HTML>
<HEAD><TITLE>Results</TITLE></HEAD>
</BODY> <H1>No Results Available</H1>
<HR>
Sorry, no one has participated in this survey up to this point.
As a result, there is no data available. Try back later.
<HR>
</BODY></HTML>
End_of_Message
}

Procedūra draw_pie yra atsakinga už pyraginės diagramos nupiešimą.

sub draw_pie
{local ($legend_rect_size, $legend_rect, $max_length, $max_height, $pie_indent, $pie_length, $pie_height, $radius, @origin,
$legend_ident, $legend_rect_to_text, $deg_to_rad, $image,
$white, $black, $red, $yellow, $green, $blue, $orange,
$percent, $loop, $degrees, $x, $y, $legend_x, $legend_y,
$legend_rect_y, $text, $message);

Pyraginis grafikas susideda iš įvairių spalvotų dalių atitinkančių pasirinkimus ir išnašos, kuri parodo kokį skonį kokia spalva atitinka. Aprašomi visi lokalūs kintamieji, reikalingi sukurti pyraginę diagramą.

$legend_rect_size = 10;
$legend_rect = $legend_rect_size * 2;

Kintamasis legend_rect_size parodo kiekvieno keturkampio (iš tikrųjų kvadrato) esančio išnašoje aukštį ir plotį. legend_rect paprasčiausiai yra taškų skaičius nuo vieno keturkampio iki kito, kartu su tarpu tarp gretimų keturkampių.

$max_length = 450;

if ($no_slices > 8) { $max_height = 200 + ( ($no_slices - 8) * $legend_rect ); } else { $max_height = 200;}

Paveikslėlio plotis yra nustatomas 450 taškų. Tačiau paveikslėlio aukštis priklauso nuo pasirinkimų (arba skonių) skaičiaus apklausoje. Tai yra dėl to, kad išnašos keturkampis yra nupiešiamas vertikaliai. Jei tyrime yra aštuoni ar mažiau pasirinkimų tai aukštis nustatomas 200 taškų. Kitu atveju, jei pasirinkimų skaičius didesnis nei aštuoni, tai viršijančių pasirinkimų skaičius yra padauginamas iš legend_rect ir pridedamas prie 200, kad nustatyti paveikslėlio aukštį.

$pie_indent = 10;
$pie_length = $pie_height = 200;
$radius = $pie_height / 2;

Pyrago piešimo procesas yra labai panašus į laikrodžio piešimą (žr. 6 skyrių, Hypermedios dokumentai). Pyragas yra atitraukiamas nuo kairiojo ir viršutinio paveikslo krašto per reikšmę saugomą pie_indent kintamajame. Pyraginio grafiko aukštis ir plotis yra 200 taškų ir nekinta. Pyrago spindulys yra apskritimo, išreikšto pie_lenght ir pie_height diametras padalintas pusiau.

@origin = ($radius + $pie_ident, $max_height / 2);
$legend_indent = $pie_length + 40;
$legend_rect_to_text = 25;
$deg_to_rad = (atan2 (1, 1) * 4) / 180;

Pyraginės diagramos centras bus pradžia. Išnaša patraukiama per 40 taškų nuo dešinės diagramos pusės. Kintamasis legend_rect_to_text nurodo taškų skaičių nuo išnašos keturkampių iki paaiškinančio teksto.

$image = new GD::Image ($max_length, $max_height);

$white = $image->colorAllocate (255, 255, 255);
$black = $image->colorAllocate (0, 0, 0);
$red = $image->colorAllocate (255, 0, 0);
$yellow = $image->colorAllocate (255, 255, 0);
$green = $image->colorAllocate (0, 255, 0);
$blue = $image->colorAllocate (0, 0, 255);
$orange = $image->colorAllocate (255, 165, 0);

Naujas paveikslėlis yra sukuriamas ir išskiriama keletas spalvų. Kaip buvo minėta ankščiau, spalvų nurodytų duomenų faile skaičius yra ribotas skaičiumi spalvų apibrėžtų aukščiau esančiame programos fragmente

grep ($_ = eval("\$$_"), @slices_color);

Tai nauja konstrukcija, kurios jūs dar nematėte anksčiau. Ji paima kiekvieną elementą iš slices_color masyvo, įvertina jį vykdymo metu ir išsaugo atitinkantį RGB indeksą tame pačiame kintamajame. Tai atitiktų tokį algoritmą:

for ($loop=0; $loop <= $no_slices; $loop++) { $temp_color = $slices_color[$loop];
$slices_color[$loop] = eval{"\$$temp_color"};
}

Kaip jūs galite pastebėti, grep ekvivalentas užsirašo daug trumpiau. Masyvas slices_color turi spalvas nurodytas duomenų faile. Ir taip pat spalvos aukščiau yra apibrėžiamos angliškais pavadinimais. Taigi, mes galime paimti iš duomenų failo spalvą kaip "geltona" ir rasti jos RGB indeksą įvertindami kintamąjį $yellow. Tai yra būtent tai ką daro eval sakinys.

$image->arc (@origin, $pie_length, $pie_height, 0, 360, $black);

Juodas apskritimas yra nubrėžiamas nuo pradžios, t.y. nuo pyraginio grafiko centro.

$percent = 0;
for ($loop=0; $loop <= $no_slices; $loop++) { $percent = $slices[$loop];
$degrees = int ( percent * 360) * $deg_to_rad;

$image->line ( $origin[0], $origin[1],
$origin[0] + ($radius * cos ($degrees)),
$origin[1] + ($radius * sin ($degrees)),
$slices_color[$loop] );
}

Procedūra read_data_file, kviečiama programos pradžioje, taip pat apskaičiuoja kiekvieno pasirinkimo procentus ir saugo juos slices masyve. Pavyzdžiui, jei iš viso buvo penki balsai iš kurių du buvo už "Vanilla", tai "Vanilla" procentai bus 40%.

Ciklas kartojasi kiekvienai procentų reikšmei ir piešia liniją nuo centro iki apskritimo krašto. Pradžioje pirma procentų reikšmė padauginama iš 360 laipsnių, kad nustatyti kampą, pagal kurį programa nubrėš pirmą liniją. Kiekvienoje ciklo iteracijoje procentų reikšmė atstoja visų procentų sumą iki to laiko. Tuomet ta procentų reikšmė yra naudojama nubrėžti sekančia liniją, kol procentų suma pasiekia 100%.

$percent = 0;
for ($loop=0; $loop <= $no_slices; $loop++) { $percent = $slices[$loop];
$degrees = int (($percent * 360) - 1) * $deg_to_rad;

$x = $origin[0] + ( ($radius - 10) * cos ($degrees) );
$y = $origin[1] + ( ($radius - 10) * sin ($degrees) );

$image->fill ($x, $y, $slices_color[$loop]);
}

Šios eilutės užpildo linijomis apibrėžtus plotus, padarytus ankstesniame cikle. Funkcija fill gd bibliotekoje veikia taip pat kaip "paint bucket" operacija daugelyje braižymo programų. Ji spalvina ploto taškus vieną po kito, kol nepasiekia ploto ribų, kurių spalva skiriasi nuo pradinio taško spalvos. Tai yra vienintelė priežastis, dėl ko šis ciklas ir prieš tai buvęs negali būti sujungti į vieną, nes skirtingų spalvų linijos turi būti pirmiau nupieštos. Pradžios taškas yra apskaičiuojamas taip, kad jo kampas nuo pradžios yra truputi mažesnis nei anksčiau nubrėžtos linijos. Taigi kaip funkcijos fill rezultatas plotas tarp dviejų skirtingų spalvų linijų yra užpildomas su spalva.

$legend_x = $legend_indent;
$legend_y = ( $max_height - ($no_slice * $legend_rect0 - ($legend_rect * 0.75) ) / 2;

Išnašos x koordinatė yra paprasčiausiai nustatoma pagal legend_indent kintamasis. Tačiau y koordinatė yra apskaičiuojama taip, kad išnaša būtų centre pagal pyraginę diagramą.

for ($loop=0; $loop <= $no_slices; $loop++) { $legend_rect_y = $legend_y + ($loop * $legend_rect);
$text = pack ( "A18", $slices_message[$loop]);

Šis ciklas nupiešia keturkampius ir atitinkamą tekstą. Y koordinatė yra padidinama kiekvieną sykį pereinant ciklą. Kintamasis text rezervuoja 18 simbolių paaiškinančiam tekstui. Jei tekstas viršija šį ilgį, tai jo galas nukerpamas. Kitu atveju tekstas pratęsiamas iki ribos pridedant tarpus.

$message = sprintf ("%s (%4.2f%%)", $text, $slices[$loop] * 100);

Kintamajame message yra suformatuojamas tekstas ir atitinkama procentų reikšmė.

$image->filledRectangle( $legend_x, $legend_rect_y,
$legend_x + $legend_rect_size,
$legend_rect_y + $legend_rect_size,
$slices_color[$loop] );

$image->string ( gdSmallFont, $legend_x + $legend_rect_to_text,
$legend_rect_y,
$message,
$black );
}

Stačiakampis nupiešiamas ir tekstas užrašomas.

$image->transparent($white);

$| = 1;
print "Content-type: image/gif", "\n\n";
print $image->gif;
}

Ir galiausiai balta spalva pažymima kaip peršviečiama, kad sukurti peršviečiamą paveiksliuką. Procedūra draw_pie pasibaigia spausdindama Content-type antgalvį (naudodamas turinio tipas image/gif) ir patį paveikslėlį.

Negrafinėms naršyklėms mes norėtume sugeneruoti rezultatus tekstiniame formate. Procedūra text_results tik tai ir padaro.

sub text_results
{ local ($text, $message, $loop)

print "Content-type: text/html", "\n\n";

print <<End_of_Results;
<HTML>
<HEAD><TITLE>Results</TITLE></HEAD>
<BODY>
<H1>Results</H1>
<HR>
<PRE>
End_of_Results

for ($loop=0; $loop <=$no_slices; $loop++) { $text = pack ("A18", $slices_message[$loop]);
$message = sprintf ("%s (%4.2f%%)", $text, $slices[$loop] * 100);

print $message, "\n";
}

print "</PRE><HR>", "\n";
print "</BODY></HTML>", "\n";
}

Duomenys yra suformatuojami sprintf funkcija ir išvedami. Skonio eilutė apribojama 18 simbolių.

Procedūra read_data_file atidaro, nuskaito failą ice_cream.dat ir grąžina rezultatus.

sub read_data_file
{ local (*slices, *slices_color, *slices_message) = @_;
local (@line, $total_votes, $poll_file, $loop, $exclusive_lock, $unlock);

$exclusive_lock = 2;
$unlock = 8;

if ($ENV{'PATH_INFO'}) { $poll_file = $document_root . $ENV{'PATH_INFO'}; } else { &return_error(500, "Poll Data File Error", "A poll data file has to be specified."); }

Aplinkos kintamasis PATH_INFO patikrinamas ar jame yra kokia nors informacija. Jei gražinama nulinio ilgio eilutė - išvedamas klaidos pranešimas. Jei gražinamas failo vardas - prie serverio pagrindinės direktorijos kelio prijungiamas duomenų failo kelias. Skirtingai nei nurodant duomenų failo kelią užklausoje, yra grąžinamas "/" simbolis, kaip kintamojo dalis.

if ( open (POLL, "<" . $poll_file) ) { flock ( POLL, $exckusive_lock);

Duomenų failas atidaromas skaitymui. Jei failo negalima atidaryti - grąžinamas klaidos pranešimas.

for ($loop=0; $loop < 3; $loop++) { $line[$loop] = <POLL>;
$line[$loop] =~ s/\n$//;
}
@slices_message = split ("::", $line[0]);
@slices         = split ("::", $line[1]);
@slices_color   = split ("::", $line[2]);

flock (POLL, $unlock);
close (POLL);

Trys eilutės perskaitomos iš duomenų failo. Eilutės padalijamos per "::" simbolius ir saugomos masyvuose. Failas atrakinamas ir uždaromas.

$total_votes = 0;
for ($loop=0; $loop <= $#slices; $loop++) { $total_votes += $slices[$loop];}

Visas balsų skaičius nustatomas susumavus masyvo slices elementus.

if ($total_votes > 0) { grep ($_ = ($_ / $total_votes), @slices);}

Kiekvienas masyvo slices elementas yra modifikuojamas taip, kad jame esantis balsų skaičius pakeičiamas į atitinkamą procentų skaičių. Jūs visuomet turite patikrinti ar daliklis yra didesnis už nulį, nes Perl gražintų "Illegal division by zero" klaidą.

} else { &return_error (500, "Poll Data File Error", "Cannot read from the poll data file [$poll_file]."); }}

Jei programa negali atidaryti duomenų failo išvedamas klaidos pranešimas.

Paskutinė pie.pl procedūra yra remove_empty_slices.

sub remove_empty_slices
{
local ($loop) = 0;

while (defined ($slices[$loop])) { if ($slices[$loop] <= 0.0) { splice(@slices, $loop, 1);
splice(@slices_color, $loop, 1);
splice(@slices_message, $loop, 2);
} else { $loop++;}
}

return ($#slices);

Kad apsaugoti programa nuo pasirinkimų (ar skonių), kurie turi nulį balsų, šie elementai ir juos atitinkančios spalvos ir tekstas yra išmetami. Funkcija splice išmeta elementus iš masyvo.