4. 1. 2023

SVG graf - vektorový obrázok priamo v html kóde

 Chcel som nahradiť komerčnú knižnicu kresliacu grafy v PHP a napadlo ma skúsiť to najskôr vlastnými silami. Ako možnosti mi vyskočili "canvas" zo štandardu HTML5 a SVG. Kým prvé generuje klasické bitmapové "obrázky", to druhé je zaujímavé pre svoju kompatibilitu.

SVG je vektorový formát obrázkov, vyvíjaný W3C (XML zápis), ktorý je tu už cez 20 rokov, webové prehliadače ho preto dobre poznajú. Bol vyvíjaný aj pre web, prehľadným XML zápisom umožňuje vkladanie aj priamo do HTML kódu stránky.

Pre vkladanie do kódu použijeme "svg" tag, rovnako ako prípadného externého do .svg súboru:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" width="640">
</svg> 
Parameter viewBox nastaví aké súradnice chceme zobraziť. Vieme tak spraviť výsek z obrázku, zoom, alebo upraviť jeho okraje. 
Až po nastavení tohto parametra vie pre width a height parameter prepočítať rozmery.

Táto minimalistická hlavička sa mi osvedčila, plnohodnotnú hlavičku nájdeme napr. tu https://www.w3.org/TR/SVG11/struct.html#NewDocument

Spravme si funkciu pre koláčový graf..


Koláčové grafy

Pre koláčové grafy potrebujeme nakresliť časť kružnice a dve čiary do jej stredu.

Použijem "path" príkaz pre kreslenie kriviek. Dôležité sú písmenové skratky v reťazci "d":

<path fill="#bf2b80" stroke="brown" d="M160,160L160,315,A155,155,0,0,0,302,99Z" fill-opacity="1">
<title>ABCD
hodnota: 166316.00
podiel: 31.45%</title></path>
  <text x="251" y="220" style="text-anchor: middle; font-size:14px; font-family:arial;">ABCD</text> 

M - move to, presunie kurzor na súradnice 160,160 (stred koláča)
L - line to, nakreslí čiaru od poslednej pozície na cieľové súradnice 160,315 (nadol)
A - elliptical arc, nakreslí výsek elipsy, v tomto prípade začína výrez na 160,315, polomery elipsy sú rovnaké 155, 155, rotácia elipsy je 0 stupňov, ďalšia 0 je tu pre uhly menšie ako 180st (vnútorná či vonkajšia elipsa), ďalej  0 pre ľavý dolný kvadrant, a končí na súradniciach 302,99
Z - koniec krivky, návrat do začiatku (čiara do 160,160 - stred koláča)

fill - kód farby výplne koláčového výrezu
stroke - čiara ktorá orámováva plochu

Je tu tiež popisný text, aj title kód pre detail s hodnotami po prebehnutí myšou.

Tento kus xml kódu opakujeme pre každú zobrazovanú hodnotu v grafe. Ako vidieť, veľkosť vygenerovaného obrázku závisí priamo úmerne od počtu zobrazovaných kriviek, preto maličké nezobraziteľné hodnoty možno v podmienke generujúceho skriptu filtrovať preč.


Funkcia pre kreslenie

Aby sme grafy použili v rôznych stránkach, je dobré spraviť si funkciu v externom (include) php súbore. Ako vstupné parametre použijem:

-res - šírka (rozlíšenie) grafu, výška bude rovnaká
-desc - popisy (názvy) ku zobrazovaným hodnotám
-data - jednotlivé kladné číselné hodnoty ktorých pomer chceme zobraziť v koláči
-title - nadpis
-link - linky z výrezov

Najskôr aby sme vyrátali percentuálne pomery, musíme zrátať všetky prvky poľa data (to bude 100%) do data_sum.

 function svg_pie($res,$desc,$data,$title = "",$link = "") {
	$pocet = count($data);
	if ( $pocet < 2 ) { echo "nemám dáta pre graf"; return 1; }	// NEMAME DATA NA VYKRESLENIE

	$data_sum=0; foreach ( $data as &$value ) $data_sum+=$value;	// SUMA HODNOT
	$okraj=10;
	$zaciatok=0;
	$stred=round($res / 2);
	$polomer=round(($res - $okraj) / 2); $ppolomer=round($polomer * 0.7);	// POLOMER S POPISMI

	echo '<svg xmlns="http://www.w3.org/2000/svg" width="'.$res.'" height="'.$res.'" viewBox="0 0 '.$res.' '.$res.'" class="pie">'."\n";
	if ( $title != "" ) echo '  <text x="8" y="18" style="font-size:16px; font-family:arial;">'.$title.'</text>'."\n";

Do premennej si nastavím šírku okrajov, stredové súradnice, polomer gravu. Následne vypíšem hlavičku pre SVG obrázok a prípadný nadpis, ak je vyplnený.

Pokračujme v cykle, kde pôjdeme po položkách v poli data a budeme vykreslovať výrezy.

	for ( $x=0; $x<$pocet; $x++ ) {
		$data_perc[$x]=floor( ( $data[$x] * 10000 ) / $data_sum ) / 100;	// PERCENTA NA 2 DES
		$zaciatok+=($data_perc[($x - 1)] * 0.062831853);					// SIN,COS V RADIANOCH
		$koniec=(($data_perc[$x] * 0.062831853) + $zaciatok);
		$rozdiel=($koniec - $zaciatok);

		if ( $rozdiel < 0.005 ) continue;			// JE TO MALICKY VYREZ, PRESKOC

Najskôr vyrátame percentá daného výrezu (tu zaokrúhlené na 2 desatiny) do data_perc. Začiatok a koniec, to sú ako percentá vrámci celého kruhu, prerátané na radiány ( * 2pi / 100 ). Rozdiel obsahuje len veľkosť (percento) aktuálne zobrazovaného výrezu.

Ak je veľkosť výrezu primalá, preskočíme na ďalší údaj v cykle (nech nemáme priveľa neviditeľných čiarok).

		$zacx=round(($polomer * sin($zaciatok)) + $stred);		// POCIATOCNE SURADNICE VYSEKU
		$zacy=round(($polomer * cos($zaciatok)) + $stred);
		$konx=round(($polomer * sin($koniec)) + $stred);		// KONCOVE SURADNICE VYSEKU
		$kony=round(($polomer * cos($koniec)) + $stred);

		$uho=0; if ( $rozdiel > 3.14159 ) $uho=1;		// JE TO VIAC AKO 180ST
		if ( $link ) echo '<a href="'.$link.$desc[$x].'">'."\n";

		$nr=rand(32,255); $ng=rand(32,255); $nb=rand(32,205);		// NAHODNE FARBY VYREZOV
		printf("<path fill=\"#%02x%02x%02x\" stroke=\"brown\" ",$nr,$ng,$nb);
		echo 'd="M'.$stred.','.$stred.'L'.$zacx.','.$zacy;		// PRVA CIARA OD STREDU
		echo 'A'.$polomer.','.$polomer.',0,'.$uho.',0,'.$konx.','.$kony.'Z" fill-opacity="1">'."\n";
		echo '  <title>'.$desc[$x]."\nhodnota: ".$data[$x]."\npodiel: ".$data_perc[$x].'%</title></path>'."\n";

Zo začiatočného a koncového uhla vieme vypočítať x,y súradnice pre začiatok (zacx,zacy) a koniec krivky cez sínus a kosínus. Ak je vyplnená linka, tak zabalíme do <a href> tagu a môžeme konečne kresliť <path> krivku.

Najskôr teda zo stredu čiaru na zacx,y, potom krivku na konx,y daného polomeru a "Z" na návrat na stred. Do vnútra umiestnime <title> ktoré po nabehnutí myšou ponad výrez vypíše (tooltip) názov, hodnotu a percentá.

Toto by aj mohlo byť všetko, ale chceme ešte viditeľné popisky aby sme videli názvy tých najväčších výrezov aj bez pohnutia myši.


        if ( $rozdiel > 0.1884 ) {			// POPIS LEN KED JE VIAC AKO 3 PERC
			$popx=round(($ppolomer * sin($zaciatok + ($rozdiel / 2))) + $stred);	// SURADNICE POPISOV
			$popy=round(($ppolomer * cos($zaciatok + ($rozdiel / 2))) + $stred);
			$col=""; if ( ($nr < 128) && ($ng < 128) && ($nb < 128) ) $col=" fill:white;";	// TMAVE POZADIE=SVETLE PISMO
			echo '  <text x="'.$popx.'" y="'.$popy.'" style="text-anchor: middle; font-size:14px; font-family:arial;'.$col.'">'.$desc[$x].'</text>'."\n";
		}
		if ( $link ) echo "</a>\n";
	}
	echo '</svg>'."\n";
 }

Konečne graf, máme prvý pokus so SVG kreslením úspešne za sebou. Funkciu zavoláme po načítaní dát z databázy do poľa napr:

svg_pie( 450, $popisy, $hodnoty, 'kapacity filesystemu' );

Myslím, že takto na kolene spravené grafy oživia dizajn stránky, zároveň netreba pozerať na to, či sú licenčné podmienky splnené pre rôzne použitia. Pri vytláčaní sa prejaví výhoda vektorového obrázku vo vysokej kvalite grafu na papieri, na graf sa dá klikať a presmerovať tak na stránku s ďalšími detailami.


Data Files DB1 hodnota: 120142.00 podiel: 11.22% DB1 DB2 hodnota: 165016.00 podiel: 15.41% DB2 DB3 hodnota: 750302.00 podiel: 70.09% DB3 DB4 hodnota: 1510.00 podiel: 0.14% tempdb hodnota: 32855.00 podiel: 3.06% tempdb

Žiadne komentáre:

Zverejnenie komentára