Пример круговой диаграммы в SVG средствами Raphael и PHP

от автора

В ходе разработки одного из наших проектов мы столкнулись с необходимостью отдавать в клиентском html большое количество графики. С точки зрения минимализации нагрузки на сервер, строить объёмную графику на стороне клиента — это единственное правильное решение. При поиске готовых и подходящих нам JS решений из этой области мы основательно подсели на JS библиотеку Raphael, которая позволяет легко прорисовывать векторную графику во всех актуальных на сегодня браузерах. Разобравшись в функционале и отладив несколько функций по созданию основных типов диаграмм, мы решили поделиться здесь своими наработками.

Пример круговой диаграммы в SVG средствами Raphael и PHP

Итак. Описанный ниже пример создаёт средствами Raphael и PHP круговую диаграмму в формате SVG, представленную на изображении. Мы постарались максимально подробно описать исходный код, чтобы помочь всем тем, кто самостоятельно разобраться в этом не нашёл сил или времени.

Файл circle.php:

<?php  // для начала необходимо создать объект с определённым идентификатором, // заданной ширины и высоты для дальнейшей работы  function start_paper($id, $width, $height) {    return 'var r = Raphael("' .$id .'", ' .$width .', ' .$height .');';  }  // создание круговой диаграммы  function paper_circle_chart($params) {    // расчёт радиуса "излома" как половины толщины сектора   // для создания 3D эффекта выпуклости диаграммы    $shadow_width = floor(($params['radius']-$params['inradius'])*0.47);   $pradius = $params['radius'] - $shadow_width;    // радиус выноса линий выноски    $outradius = $params['radius'] + $params['text_radius'];    // сумма данных    $total = array_sum($params['data']);    $i = 0;     $prev = 0;     $fangel = 0;     $code = '';   $pcode = '';   $pline = '';   $ptext = '';   $pstext = '';   $center = '';   $begin = true;    // рисуем секторы диаграммы в цикле    foreach ($params['data'] as $k => $v) {      // определяем цвет текущего сектора      $color = $params['colors'][$i];      // если нулевой сектор проходим мимо      if ($v == 0) {       $i++;       continue;       }      // если диаграмма состоит из одного сектора - рисуем сплошной круг      elseif ($v == $total) {       $pend = deg2rad(45);       $code = '           r.circle(' .$params['centerx'] .', ' .$params['centery'] .', ' .$params['radius'] .').attr({"fill": "' .$color .'"})';       if (count($params['texts'])) {         $dxc = round($params['centerx'] + $pradius * sin($pend), 2);         $dyc = round($params['centery'] - $pradius * cos($pend), 2);         $dxc1 = round($params['centerx'] + $outradius * sin($pend), 2);         $dyc1 = round($params['centery'] - $outradius * cos($pend), 2);         $dxc2 = $dxc1 + $params['text_width'];         $dxc3 = $dxc1 + round($params['text_width']/2, 2);         $dyc2 = $dyc1 - $params['text_minus'];         $dyc3 = $dyc1 + $params['text_plus'];         $pcode .= '           r.circle(' .$dxc .', ' .$dyc .', ' .$params['point_radius'] .')';         $pline .= '         r.path("M'. $dxc .','. $dyc .' L'. $dxc1 .','. $dyc1 .' L'. $dxc2 .','. $dyc1 .'")';         $ptext .= '           r.text(' .$dxc3 .', ' .$dyc2 .', "100 %")';         $pstext .= '           r.text(' .$dxc3 .', ' .$dyc3 .', "' .$params['texts'][$i] .'")';         }       }      // иначе - рисуем текущий сектор      else {       $percent = $v / $total;         $angel = 360 * $percent;         $rad = deg2rad($angel);         $end = $prev + $rad;       $pend = $prev + $rad/2;       $dx = round($params['centerx'] + $params['radius'] * sin($prev), 2);       $dy = round($params['centery'] - $params['radius'] * cos($prev), 2);       $dxp = round($params['centerx'] + $params['radius'] * sin($end), 2);         $dyp = round($params['centery'] - $params['radius'] * cos($end), 2);         if ($percent > 0.5) $sec = 1;       else $sec = 0;       if (!$begin) $code .= ',';       $code .= '           r.path("M' .$params['centerx'] .',' .$params['centery'] .' L' .$dx .',' .$dy .' A' .$params['radius'] .',' .$params['radius'] .' 0 ' .$sec .',1 ' .$dxp .',' .$dyp .' z").attr({"fill": "' .$color .'"})';       if (count($params['texts'])) {         $dxc = round($params['centerx'] + $pradius * sin($pend), 2);         $dyc = round($params['centery'] - $pradius * cos($pend), 2);         $dxc1 = round($params['centerx'] + $outradius * sin($pend), 2);         $dyc1 = round($params['centery'] - $outradius * cos($pend), 2);         if (($fangel + $angel/2) > 180) {           $dxc2 = $dxc1 - $params['text_width'];           $dxc3 = $dxc1 - round($params['text_width']/2, 2);           }         else {           $dxc2 = $dxc1 + $params['text_width'];           $dxc3 = $dxc1 + round($params['text_width']/2, 2);           }         $dyc2 = $dyc1 - $params['text_minus'];         $dyc3 = $dyc1 + $params['text_plus'];         if (!$begin) {           $pcode .= ',';           $ptext .= ',';           $pstext .= ',';           $pline .= ',';           }         $pcode .= '           r.circle(' .$dxc .', ' .$dyc .', ' .$params['point_radius'] .')';         $pline .= '         r.path("M'. $dxc .','. $dyc .' L'. $dxc1 .','. $dyc1 .' L'. $dxc2 .','. $dyc1 .'")';         $ptext .= '           r.text(' .$dxc3 .', ' .$dyc2 .', "' .round($percent * 100) .' %")';         $pstext .= '           r.text(' .$dxc3 .', ' .$dyc3 .', "' .$params['texts'][$i] .'")';         }       $i++;       $begin = false;       $prev = $end;       $fangel += $angel;         }     }     // устанавливаем атрибут "контур" для всех секторов диагрммы    if ($code) $code = '         var st = r.set();         st.push(' .$code .'           );         st.attr({"stroke": "none"});';    // устанавливаем атрибут "цвет" для всех линий выноски    if ($pline) $pline = '         var st = r.set();         st.push(' .$pline .'           );         st.attr({"stroke": "' .$params['line_color'] .'"});';    // устанавливаем атрибуты "контур" и "цвет заливки" для плашек всех линий выноски    if ($pcode) $pcode = '         var st = r.set();         st.push(' .$pcode .'           );         st.attr({"fill": "' .$params['text_color'] .'", "stroke": "' .$params['stroke_color'] .'"});';    // устанавливаем атрибуты "шрифт", "размер" и "цвет" для подписей над линией выноски    if ($ptext) $ptext = '         var st = r.set();         st.push(' .$ptext .'           );         st.attr({"font-family": "' .$params['font'] .'", "font-size": "' .$params['text_name'] .'", "fill": "' .$params['text_color'] .'", "cursor": "default"});';    // устанавливаем атрибуты "шрифт", "размер" и "цвет" для подписей под линией выноски    if ($pstext) $pstext = '         var st = r.set();         st.push(' .$pstext .'           );         st.attr({"font-family": "' .$params['font'] .'", "font-size": "' .$params['text_small'] .'", "fill": "' .$params['text_color'] .'", "cursor": "default"});';    // создаём центр диаграммы и текст внутри него    $inradius = '';   if ($params['inradius'] > 0) {     $inradius = '         r.circle(' .$params['centerx'] .', ' .$params['centery'] .', ' .$params['inradius'] .').attr({"fill": "' .$params['center_text_back'] .'", "stroke": "none"});';     if ($params['shadow']) $inradius .= '         r.circle(' .$params['centerx'] .', ' .$params['centery'] .', ' .($params['radius']-$shadow_width+floor($shadow_width/2)) .').attr({"fill": "none", "stroke": "#FFFFFF", "stroke-width": "' .$shadow_width .'", "stroke-opacity": "0.15"});';     if ($params['center_text']) {       $center = '         r.text(' .$params['centerx'] .', ' .$params['centery'] .', "' .$params['center_text'] .'").attr({"font-family": "' .$params['font'] .'", "font-size": "' .$params['center_text_size'] .'", "fill": "' .$params['center_text_color'] .'", "cursor": "default"});';       }     }    return $code .$inradius .$pline .$pcode .$ptext .$pstext .$center;  }  ?>  

Файл test.php:

<?php  include("circle.php");  $params = array (					// параметры диаграммы    'font' => 'PT Sans, Tahoma',				// шрифт    'text_color' => '#212121',				// цвет текста   'line_color' => '#494949',				// цвет линий выноски   'text_width' => 40,					// ширина горизотальной части линий выноски   'text_radius' => 14,					// расстояние отступа горизотальной части линий выноски от диаграммы   'point_radius' => 3,					// радиус плашки линии выноски ( круг в месте соединения линии выноски с сектором данных )   'stroke_color' => '#EFEFEF',				// цвет контура плашки   'text_name' => 13,					// размер текста над линией выноски   'text_small' => 11,					// размер текста под линией выноски   'text_minus' => 10,					// отступ текста вверх над линией выноски   'text_plus' => 8,					// отступ текста вниз под линией выноски    'centerx' => 100,					// центр диаграммы по оси x   'centery' => 100,					// центр диаграммы по оси y   'radius' => 42,					// внешний радиус диаграммы    'inradius' => 19,					// радиус внутреннего круга   'center_text_back' => '90-#e7e7e7-#ffffff:60',	// цвет внутреннего круга   'center_text' => 416,					// надпись в центре внутреннего круга   'center_text_size' => 14,				// размер текста в центре внутреннего круга   'center_text_color' => '#212121',			// цвет текста в центре внутреннего круга    'data' => array(					// исходные данные     139,     112,     89,     76),    'texts' => array (					// подписи к секторам, отображаются под линией выноски     'пас',     'навес',     'в разрез',     'прострел'   ),    'colors' => array (					// цвета секторов     '0-#08b2ff-#0e56d4',     '0-#fffa17-#ffba17',     '0-#e0070e-#f15722',     '0-#BCE408-#5FBB00'   ),    'shadow' => 1						// делать или нет 3D эффект выпуклости диаграммы  );   // создаём диаграмму, в <head></head> размещаем js код, в <body></body> - <div></div> // c идентификатором идентичным переданному в конструктор диаграммы start_paper()   $head = start_paper('diagram', 200, 200) .paper_circle_chart($params); $body = '<div id="diagram"></div>';  echo ' <!DOCTYPE html> <html>   <head>     <title>Пример круговой SVG диаграммы средствами Raphael и PHP</title>     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />     <script src="/raphael-min.js"></script>     <script>       window.onload = function () {'         .$head .'        };     </script>   </head>   <body>'     .$body .'   </body> </html>';  ?>  

Приведенный выше исходный код полностью рабочий. Каждый желающий поковыряться и разобраться в изложенном материале самостоятельно может скачать библиотеку и собрать три файла (circle.php, test.php и raphael-min.js) в единое целое.

Итоговый html, который получает клиент:

<!DOCTYPE html> <html>   <head>     <title>Пример круговой SVG диаграммы средствами Raphael и PHP</title>     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />     <script src="/raphael-min.js"></script>     <script>       window.onload = function () {var r = Raphael("diagram", 200, 200);         var st = r.set();         st.push(           r.path("M100,100 L100,58 A42,42 0 0,1 136.27,121.18 z").attr({"fill": "0-#08b2ff-#0e56d4"}),           r.path("M100,100 L136.27,121.18 A42,42 0 0,1 74.6,133.45 z").attr({"fill": "0-#fffa17-#ffba17"}),           r.path("M100,100 L74.6,133.45 A42,42 0 0,1 61.7,82.76 z").attr({"fill": "0-#e0070e-#f15722"}),           r.path("M100,100 L61.7,82.76 A42,42 0 0,1 100,58 z").attr({"fill": "0-#BCE408-#5FBB00"})           );         st.attr({"stroke": "none"});         r.circle(100, 100, 19).attr({"fill": "90-#e7e7e7-#ffffff:60", "stroke": "none"});         r.circle(100, 100, 37).attr({"fill": "none", "stroke": "#FFFFFF", "stroke-width": "10", "stroke-opacity": "0.15"});         var st = r.set();         st.push(         r.path("M127.75,84.07 L148.57,72.12 L188.57,72.12"),         r.path("M106.24,131.39 L110.93,154.92 L150.93,154.92"),         r.path("M68.99,107.89 L45.73,113.81 L5.73,113.81"),         r.path("M82.63,73.13 L69.59,52.97 L29.59,52.97")           );         st.attr({"stroke": "#494949"});         var st = r.set();         st.push(           r.circle(127.75, 84.07, 3),           r.circle(106.24, 131.39, 3),           r.circle(68.99, 107.89, 3),           r.circle(82.63, 73.13, 3)           );         st.attr({"fill": "#212121", "stroke": "#EFEFEF"});         var st = r.set();         st.push(           r.text(168.57, 62.12, "33 %"),           r.text(130.93, 144.92, "27 %"),           r.text(25.73, 103.81, "21 %"),           r.text(49.59, 42.97, "18 %")           );         st.attr({"font-family": "PT Sans, Tahoma", "font-size": "13", "fill": "#212121", "cursor": "default"});         var st = r.set();         st.push(           r.text(168.57, 80.12, "пас"),           r.text(130.93, 162.92, "навес"),           r.text(25.73, 121.81, "в разрез"),           r.text(49.59, 60.97, "прострел")           );         st.attr({"font-family": "PT Sans, Tahoma", "font-size": "11", "fill": "#212121", "cursor": "default"});         r.text(100, 100, "416").attr({"font-family": "PT Sans, Tahoma", "font-size": "14", "fill": "#212121", "cursor": "default"});        };     </script>   </head>   <body><div id="diagram"></div>   </body> </html>  

О том, как работает градиентная заливка (0-#BCE408-#5FBB00) с точки зрения синтаксиса Raphael и другие моменты касательно функций этой библиотеки и их параметров достаточно подробно изложены в документации. К слову подробная документация, широкий функционал и кроссбраузерность этого решения — являются, с нашей точки зрения, неоспоримым преимуществом данной библиотеки над аналогичными средствами.

Надеемся, что наш опыт будет полезен всем тем, кто впервые столкнулся с задачей построения векторной графики в html средствами JS. Постараемся ответить на все вопросы и комментарии по данной теме.

ссылка на оригинал статьи http://habrahabr.ru/post/180659/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *