1
0

graph_gen.php 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. <?php
  2. class graph{
  3. public function render(array $graph = [], array $size = [], array $colors = [], array $colors_line = []){
  4. $graph = array_merge([
  5. "title" => "Title",
  6. "subtitle" => "description",
  7. "grad_x" => 25,
  8. "grad_y" => 20,
  9. "x_label" => "<= x =>",
  10. "y_label" => "<= y =>",
  11. "antialias" => false,
  12. "data" => []
  13. ], $graph);
  14. $size = array_merge([
  15. "image_width" => 750,
  16. "image_height" => 500,
  17. "x" => 90,
  18. "y" => 50,
  19. "width" => 650,
  20. "height" => 300,
  21. "square" => 11,
  22. "column" => 6
  23. ], $size);
  24. $colors = array_merge([
  25. "bg" => new ImagickPixel("#1d2021"),
  26. "lines_bg" => new ImagickPixel("#3c3836"),
  27. "outline" => new ImagickPixel("#a89984"),
  28. "text" => new ImagickPixel("#ebdbb2"),
  29. "transparent" => new ImagickPixel("transparent")
  30. ], $colors);
  31. $colors_line = array_merge([
  32. new ImagickPixel("#83a598"),
  33. new ImagickPixel("#fb4934"),
  34. new ImagickPixel("#b8bb26"),
  35. new ImagickPixel("#fabd2f"),
  36. new ImagickPixel("#d3869b"),
  37. new ImagickPixel("#8ec07c"),
  38. new ImagickPixel("#fe8019"),
  39. new ImagickPixel("#458588"),
  40. new ImagickPixel("#cc241d"),
  41. new ImagickPixel("#98971a"),
  42. new ImagickPixel("#d79921"),
  43. new ImagickPixel("#b16286"),
  44. new ImagickPixel("#689d6a"),
  45. new ImagickPixel("#d65d0e"),
  46. new ImagickPixel("#a89984") // other
  47. ], $colors_line);
  48. $not_enough_data = false;
  49. // compute datapoint max ranges
  50. $max_range_x = 0;
  51. $max_range_y = 0;
  52. if(count($graph["data"]) === 0){
  53. $not_enough_data = true;
  54. }else{
  55. foreach($graph["data"] as $datapoint => $value){
  56. if(count($value) < 1){
  57. $not_enough_data = true;
  58. }else{
  59. $nv1 = [];
  60. $nv2 = [];
  61. foreach($value as $k => $v){
  62. $nv1[] = $v[0];
  63. $nv2[] = $v[1];
  64. }
  65. $max1 = max($nv1);
  66. $max2 = max($nv2);
  67. if($max_range_x < $max1){ $max_range_x = $max1; }
  68. if($max_range_y < $max2){ $max_range_y = $max2; }
  69. }
  70. }
  71. }
  72. $im = new Imagick();
  73. $im->newImage($size["image_width"], $size["image_height"], $colors["bg"]);
  74. $im->setImageFormat("png");
  75. $draw = new ImagickDraw();
  76. //
  77. // Draw background grid
  78. //
  79. $draw->setStrokeColor($colors["lines_bg"]);
  80. $draw->setStrokeAntialias($graph["antialias"]);
  81. $draw->setStrokeWidth(1);
  82. $draw->setFillColor($colors["transparent"]);
  83. $text = new ImagickDraw();
  84. $text->setFontSize(11);
  85. $text->setTextAntialias($graph["antialias"]);
  86. $text->setFillColor($colors["text"]);
  87. // vertical lines
  88. $offset = $size["width"] / ($graph["grad_x"] - 1);
  89. for($i=0; $i<$graph["grad_x"]; $i++){
  90. $x = round($size["x"] + ($offset * $i));
  91. if(
  92. $i === 0 ||
  93. $i === $graph["grad_x"] - 1
  94. ){
  95. $draw->setStrokeColor($colors["outline"]);
  96. }else{
  97. $draw->setStrokeColor($colors["lines_bg"]);
  98. }
  99. $draw->line(
  100. $x,
  101. $size["y"],
  102. $x,
  103. $size["y"] + $size["height"],
  104. );
  105. if($not_enough_data === false){
  106. // add numbas
  107. $number = number_format(round(($graph["grad_x"] - 1 - $i) * ($max_range_x / ($graph["grad_x"] - 1))));
  108. $number_width = $im->queryFontMetrics($text, $number)["textWidth"];
  109. $im->annotateImage(
  110. $text,
  111. round($x - ($number_width / 2)),
  112. $size["y"] + $size["height"] + 14,
  113. 0,
  114. $number
  115. );
  116. }
  117. }
  118. // horizontal lines
  119. $text->setTextAlignment(Imagick::ALIGN_RIGHT);
  120. $offset = $size["height"] / ($graph["grad_y"] - 1);
  121. $vertical_text_size = 0;
  122. for($i=0; $i<$graph["grad_y"]; $i++){
  123. $y = round($size["y"] + ($offset * $i));
  124. if(
  125. $i === 0 ||
  126. $i === $graph["grad_y"] - 1
  127. ){
  128. $draw->setStrokeColor($colors["outline"]);
  129. }else{
  130. $draw->setStrokeColor($colors["lines_bg"]);
  131. }
  132. $draw->line(
  133. $size["x"] + 1,
  134. $y,
  135. $size["x"] + $size["width"] - 1,
  136. $y,
  137. );
  138. if($not_enough_data === false){
  139. $val = number_format(round(($graph["grad_y"] - 1 - $i) * ($max_range_y / ($graph["grad_y"] - 1))));
  140. $query = $im->queryFontMetrics($text, $val);
  141. if($query["textWidth"] > $vertical_text_size){
  142. $vertical_text_size = $query["textWidth"];
  143. }
  144. // add numbas
  145. $im->annotateImage(
  146. $text,
  147. $size["x"] - 4,
  148. $y + 4,
  149. 0,
  150. $val
  151. );
  152. }
  153. }
  154. //
  155. // Draw legend
  156. //
  157. $text->setTextAlignment(Imagick::ALIGN_LEFT);
  158. $top_padding = 44;
  159. $c = count($colors_line);
  160. $padding = $size["width"] / $size["column"];
  161. $line = new ImagickDraw();
  162. //$line->setStrokeWidth(1);
  163. $line->setStrokeAntialias($graph["antialias"]);
  164. if($not_enough_data === false){
  165. $i = 0;
  166. foreach($graph["data"] as $datafield => $data){
  167. $used_color = $colors_line[$i % $c];
  168. $draw->setStrokeColor($used_color);
  169. $row = floor($i / $size["column"]);
  170. $x = round($size["x"] + (($i % $size["column"]) * $padding));
  171. $y = $size["y"] + $size["height"] + $top_padding + ($row * 17);
  172. // draw legend
  173. $draw->rectangle(
  174. $x,
  175. $y,
  176. $x + $size["square"],
  177. $y + $size["square"]
  178. );
  179. $im->annotateImage(
  180. $text,
  181. $x + $size["square"] + 4,
  182. $y + 10,
  183. 0,
  184. $datafield
  185. );
  186. //
  187. // Draw lines
  188. //
  189. $line->setStrokeColor($used_color);
  190. $old_x = null;
  191. $old_y = null;
  192. $continue = true;
  193. foreach($data as $field){
  194. // field 0 = hour (sorted)
  195. // field 1 = datapoint
  196. $x = $size["x"] + (($max_range_x - $field[0]) * ($size["width"] / $max_range_x));
  197. $y = $size["y"] + (($max_range_y - $field[1]) * ($size["height"] / $max_range_y));
  198. if($continue){
  199. $continue = false;
  200. $old_x = $x;
  201. $old_y = $y;
  202. continue;
  203. }
  204. $line->line($old_x, $old_y, $x, $y);
  205. $old_x = $x;
  206. $old_y = $y;
  207. }
  208. $i++;
  209. }
  210. }
  211. //
  212. // add background labels
  213. //
  214. $text->setFontSize(13);
  215. $text->setFillColor($colors["bg"]);
  216. $draw->setFillColor($colors["text"]);
  217. $draw->setStrokeColor($colors["transparent"]);
  218. // horizontal
  219. $label_width_x = $im->queryFontMetrics($text, $graph["x_label"]);
  220. $x_x = $size["x"] + ($size["width"] / 2) - ($label_width_x["textWidth"] / 2);
  221. $x_y = $size["y"] + $size["height"] + ($not_enough_data ? 21 : 30);
  222. $draw->rectangle(
  223. $x_x - 2,
  224. $x_y - $label_width_x["textHeight"] + 4,
  225. $x_x + $label_width_x["textWidth"] + 2,
  226. $x_y + 3,
  227. );
  228. // vertical
  229. $label_width_y = $im->queryFontMetrics($text, $graph["y_label"]);
  230. $y_x = $size["x"] - $label_width_y["textHeight"] - 13 - $vertical_text_size;
  231. $y_y = $size["y"] + ($size["height"] / 2) - ($label_width_y["textWidth"] / 2);
  232. $draw->rectangle(
  233. $y_x + 4,
  234. $y_y - 2,
  235. $y_x + $label_width_y["textHeight"] + 3,
  236. $y_y + $label_width_y["textWidth"] + 2,
  237. );
  238. $im->drawImage($draw);
  239. $im->drawImage($line);
  240. // horizontal text
  241. $im->annotateImage(
  242. $text,
  243. $x_x,
  244. $x_y,
  245. 0,
  246. $graph["x_label"]
  247. );
  248. // vertical text
  249. $im->annotateImage(
  250. $text,
  251. $y_x + $label_width_y["textHeight"],
  252. $y_y + $label_width_y["textWidth"],
  253. 270,
  254. $graph["y_label"]
  255. );
  256. //
  257. // draw title + subtext
  258. //
  259. $text->setFontSize(20);
  260. $text->setFontWeight(900);
  261. $text->setFillColor($colors["text"]);
  262. $im->annotateImage(
  263. $text,
  264. $size["x"],
  265. $size["y"] - 25,
  266. 0,
  267. $graph["title"]
  268. );
  269. $text->setFontSize(13);
  270. $text->setFontWeight(100);
  271. $im->annotateImage(
  272. $text,
  273. $size["x"],
  274. $size["y"] - 10,
  275. 0,
  276. $graph["subtitle"]
  277. );
  278. if($not_enough_data){
  279. // add not enough data notice
  280. $rect = new ImagickDraw();
  281. $rect->setFillColor($colors["bg"]);
  282. $rect->setStrokeColor($colors_line[1]);
  283. $rect_w = 200;
  284. $rect_h = 24;
  285. $rect_x = round($size["x"] + ($size["width"] / 2) - ($rect_w / 2));
  286. $rect_y = round($size["y"] + ($size["height"] / 2) - ($rect_h / 2));
  287. $rect->rectangle(
  288. $rect_x,
  289. $rect_y,
  290. $rect_x + $rect_w,
  291. $rect_y + $rect_h
  292. );
  293. $im->drawImage($rect);
  294. $label = "Not enough data";
  295. $text->setFontWeight(900);
  296. $query = $im->queryFontMetrics($text, $label);
  297. $im->annotateImage(
  298. $text,
  299. round($size["x"] + ($size["width"] / 2) - ($query["textWidth"] / 2)),
  300. round($size["y"] + ($size["height"] / 2) + ($query["textHeight"] / 2)) - 3,
  301. 0,
  302. $label
  303. );
  304. }
  305. return $im->getImageBlob();
  306. }
  307. }