gruff 0.4.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (584) hide show
  1. data/.gitignore +7 -0
  2. data/.travis.yml +14 -0
  3. data/Gemfile +10 -0
  4. data/History.txt +193 -0
  5. data/MIT-LICENSE +22 -0
  6. data/Manifest.txt +81 -0
  7. data/README.md +139 -0
  8. data/RELEASE.md +92 -0
  9. data/Rakefile +218 -0
  10. data/assets/bubble.png +0 -0
  11. data/assets/city_scene/background/0000.png +0 -0
  12. data/assets/city_scene/background/0600.png +0 -0
  13. data/assets/city_scene/background/2000.png +0 -0
  14. data/assets/city_scene/clouds/cloudy.png +0 -0
  15. data/assets/city_scene/clouds/partly_cloudy.png +0 -0
  16. data/assets/city_scene/clouds/stormy.png +0 -0
  17. data/assets/city_scene/grass/default.png +0 -0
  18. data/assets/city_scene/haze/true.png +0 -0
  19. data/assets/city_scene/number_sample/1.png +0 -0
  20. data/assets/city_scene/number_sample/2.png +0 -0
  21. data/assets/city_scene/number_sample/default.png +0 -0
  22. data/assets/city_scene/sky/0000.png +0 -0
  23. data/assets/city_scene/sky/0200.png +0 -0
  24. data/assets/city_scene/sky/0400.png +0 -0
  25. data/assets/city_scene/sky/0600.png +0 -0
  26. data/assets/city_scene/sky/0800.png +0 -0
  27. data/assets/city_scene/sky/1000.png +0 -0
  28. data/assets/city_scene/sky/1200.png +0 -0
  29. data/assets/city_scene/sky/1400.png +0 -0
  30. data/assets/city_scene/sky/1500.png +0 -0
  31. data/assets/city_scene/sky/1700.png +0 -0
  32. data/assets/city_scene/sky/2000.png +0 -0
  33. data/assets/pc306715.jpg +0 -0
  34. data/assets/plastik/blue.png +0 -0
  35. data/assets/plastik/green.png +0 -0
  36. data/assets/plastik/red.png +0 -0
  37. data/gruff.gemspec +36 -0
  38. data/init.rb +2 -0
  39. data/lib/gruff.rb +32 -0
  40. data/lib/gruff/accumulator_bar.rb +18 -0
  41. data/lib/gruff/area.rb +51 -0
  42. data/lib/gruff/bar.rb +108 -0
  43. data/lib/gruff/bar_conversion.rb +46 -0
  44. data/lib/gruff/base.rb +1169 -0
  45. data/lib/gruff/bezier.rb +46 -0
  46. data/lib/gruff/bullet.rb +111 -0
  47. data/lib/gruff/deprecated.rb +39 -0
  48. data/lib/gruff/dot.rb +125 -0
  49. data/lib/gruff/line.rb +242 -0
  50. data/lib/gruff/mini/bar.rb +37 -0
  51. data/lib/gruff/mini/legend.rb +109 -0
  52. data/lib/gruff/mini/pie.rb +36 -0
  53. data/lib/gruff/mini/side_bar.rb +35 -0
  54. data/lib/gruff/net.rb +138 -0
  55. data/lib/gruff/photo_bar.rb +100 -0
  56. data/lib/gruff/pie.rb +124 -0
  57. data/lib/gruff/scatter.rb +264 -0
  58. data/lib/gruff/scene.rb +209 -0
  59. data/lib/gruff/side_bar.rb +138 -0
  60. data/lib/gruff/side_stacked_bar.rb +97 -0
  61. data/lib/gruff/spider.rb +131 -0
  62. data/lib/gruff/stacked_area.rb +67 -0
  63. data/lib/gruff/stacked_bar.rb +57 -0
  64. data/lib/gruff/stacked_mixin.rb +23 -0
  65. data/lib/gruff/themes.rb +102 -0
  66. data/lib/gruff/version.rb +3 -0
  67. data/rails_generators/gruff/gruff_generator.rb +63 -0
  68. data/rails_generators/gruff/templates/controller.rb +32 -0
  69. data/rails_generators/gruff/templates/functional_test.rb +24 -0
  70. data/test/gruff_test_case.rb +155 -0
  71. data/test/image_compare.rb +58 -0
  72. data/test/output/accum_bar.png +0 -0
  73. data/test/output/area_keynote.png +0 -0
  74. data/test/output/area_keynote_small.png +0 -0
  75. data/test/output/area_many.png +0 -0
  76. data/test/output/area_many_areas_small.png +0 -0
  77. data/test/output/area_tiny.png +0 -0
  78. data/test/output/area_wide.png +0 -0
  79. data/test/output/bar2_wrapped_legend_16_400.png +0 -0
  80. data/test/output/bar2_wrapped_legend_16_800.png +0 -0
  81. data/test/output/bar2_wrapped_legend_30_400.png +0 -0
  82. data/test/output/bar2_wrapped_legend_30_800.png +0 -0
  83. data/test/output/bar2_wrapped_legend_4_400.png +0 -0
  84. data/test/output/bar2_wrapped_legend_4_800.png +0 -0
  85. data/test/output/bar2_wrapped_legend__400.png +0 -0
  86. data/test/output/bar2_wrapped_legend__800.png +0 -0
  87. data/test/output/bar_background_gradient_bottom_top.png +0 -0
  88. data/test/output/bar_background_gradient_left_right.png +0 -0
  89. data/test/output/bar_background_gradient_right_left.png +0 -0
  90. data/test/output/bar_background_gradient_top_bottom.png +0 -0
  91. data/test/output/bar_background_gradient_topleft_bottomright.png +0 -0
  92. data/test/output/bar_background_gradient_topright_bottomleft.png +0 -0
  93. data/test/output/bar_formatted_numbers.png +0 -0
  94. data/test/output/bar_keynote.png +0 -0
  95. data/test/output/bar_keynote_small.png +0 -0
  96. data/test/output/bar_long_legend_text.png +0 -0
  97. data/test/output/bar_manual_colors.png +0 -0
  98. data/test/output/bar_marker_shadow.png +0 -0
  99. data/test/output/bar_nearly_zero_max_10.png +0 -0
  100. data/test/output/bar_no_legend.png +0 -0
  101. data/test/output/bar_no_line_markers.png +0 -0
  102. data/test/output/bar_no_title.png +0 -0
  103. data/test/output/bar_no_title_or_legend.png +0 -0
  104. data/test/output/bar_odeo.png +0 -0
  105. data/test/output/bar_one_value.png +0 -0
  106. data/test/output/bar_pos_neg.png +0 -0
  107. data/test/output/bar_rails_keynote.png +0 -0
  108. data/test/output/bar_set_absolute_trunc.png +0 -0
  109. data/test/output/bar_set_label_stagger_height.png +0 -0
  110. data/test/output/bar_set_legend_box_size_lg.png +0 -0
  111. data/test/output/bar_set_legend_box_size_sm.png +0 -0
  112. data/test/output/bar_set_marker.png +0 -0
  113. data/test/output/bar_set_trailing_dots_trunc.png +0 -0
  114. data/test/output/bar_spacing_full.png +0 -0
  115. data/test/output/bar_spacing_half.png +0 -0
  116. data/test/output/bar_spacing_none.png +0 -0
  117. data/test/output/bar_tall_graph.png +0 -0
  118. data/test/output/bar_tall_graph_small.png +0 -0
  119. data/test/output/bar_themed.png +0 -0
  120. data/test/output/bar_title_margin.png +0 -0
  121. data/test/output/bar_value_labels.png +0 -0
  122. data/test/output/bar_wide_graph.png +0 -0
  123. data/test/output/bar_wide_graph_small.png +0 -0
  124. data/test/output/bar_wrapped_legend_16_400.png +0 -0
  125. data/test/output/bar_wrapped_legend_16_800.png +0 -0
  126. data/test/output/bar_wrapped_legend_30_400.png +0 -0
  127. data/test/output/bar_wrapped_legend_30_800.png +0 -0
  128. data/test/output/bar_wrapped_legend_4_400.png +0 -0
  129. data/test/output/bar_wrapped_legend_4_800.png +0 -0
  130. data/test/output/bar_wrapped_legend__400.png +0 -0
  131. data/test/output/bar_wrapped_legend__800.png +0 -0
  132. data/test/output/bar_x_y_labels.png +0 -0
  133. data/test/output/bar_y_increment_1.png +0 -0
  134. data/test/output/bar_y_increment_2.0.png +0 -0
  135. data/test/output/bar_y_increment_20.png +0 -0
  136. data/test/output/bar_y_increment_5.png +0 -0
  137. data/test/output/bar_zero_marker_count.png +0 -0
  138. data/test/output/bar_zero_spacing.png +0 -0
  139. data/test/output/bezier.png +0 -0
  140. data/test/output/bezier_2.png +0 -0
  141. data/test/output/bezier_3.png +0 -0
  142. data/test/output/bullet_greyscale.png +0 -0
  143. data/test/output/bullet_no_options.png +0 -0
  144. data/test/output/dot.png +0 -0
  145. data/test/output/dot_manual_colors.png +0 -0
  146. data/test/output/dot_nearly_zero_max_10.png +0 -0
  147. data/test/output/dot_no_legend.png +0 -0
  148. data/test/output/dot_no_line_markers.png +0 -0
  149. data/test/output/dot_no_title.png +0 -0
  150. data/test/output/dot_no_title_or_legend.png +0 -0
  151. data/test/output/dot_one_value.png +0 -0
  152. data/test/output/dot_pos_neg.png +0 -0
  153. data/test/output/dot_set_legend_box_size_lg.png +0 -0
  154. data/test/output/dot_set_legend_box_size_sm.png +0 -0
  155. data/test/output/dot_set_marker.png +0 -0
  156. data/test/output/dot_small.png +0 -0
  157. data/test/output/dot_tall_graph.png +0 -0
  158. data/test/output/dot_tall_graph_small.png +0 -0
  159. data/test/output/dot_themed.png +0 -0
  160. data/test/output/dot_wide_graph.png +0 -0
  161. data/test/output/dot_wide_graph_small.png +0 -0
  162. data/test/output/dot_x_y_labels.png +0 -0
  163. data/test/output/dot_y_increment_1.png +0 -0
  164. data/test/output/dot_y_increment_2.0.png +0 -0
  165. data/test/output/dot_y_increment_20.png +0 -0
  166. data/test/output/dot_y_increment_5.png +0 -0
  167. data/test/output/enhancements-1.png +0 -0
  168. data/test/output/enhancements.png +0 -0
  169. data/test/output/line_all_neg.png +0 -0
  170. data/test/output/line_all_neg_400.png +0 -0
  171. data/test/output/line_all_neg_no_max.png +0 -0
  172. data/test/output/line_baseline.png +0 -0
  173. data/test/output/line_hang_value.png +0 -0
  174. data/test/output/line_hide_dots.png +0 -0
  175. data/test/output/line_hide_lines.png +0 -0
  176. data/test/output/line_jruby_error.png +0 -0
  177. data/test/output/line_large.png +0 -0
  178. data/test/output/line_large_baseline.png +0 -0
  179. data/test/output/line_legend_at_bottom.png +0 -0
  180. data/test/output/line_many.png +0 -0
  181. data/test/output/line_many_lines_small.png +0 -0
  182. data/test/output/line_many_numbers.png +0 -0
  183. data/test/output/line_marker_label_accuracy.png +0 -0
  184. data/test/output/line_more_sets_than_colors.png +0 -0
  185. data/test/output/line_no_data.png +0 -0
  186. data/test/output/line_no_data_msg.png +0 -0
  187. data/test/output/line_no_data_other.png +0 -0
  188. data/test/output/line_no_hide.png +0 -0
  189. data/test/output/line_no_legend.png +0 -0
  190. data/test/output/line_no_line_markers.png +0 -0
  191. data/test/output/line_no_title.png +0 -0
  192. data/test/output/line_nothing_but_the_graph.png +0 -0
  193. data/test/output/line_one_value.png +0 -0
  194. data/test/output/line_one_value_array.png +0 -0
  195. data/test/output/line_pos_neg.png +0 -0
  196. data/test/output/line_pos_neg_400.png +0 -0
  197. data/test/output/line_similar_high_end_values.png +0 -0
  198. data/test/output/line_similar_high_end_values_with_floor.png +0 -0
  199. data/test/output/line_small_small_zero.png +0 -0
  200. data/test/output/line_small_values.png +0 -0
  201. data/test/output/line_small_values_small.png +0 -0
  202. data/test/output/line_small_zero.png +0 -0
  203. data/test/output/line_some_nil_points.png +0 -0
  204. data/test/output/line_theme_37signals_.png +0 -0
  205. data/test/output/line_theme_37signals_400.png +0 -0
  206. data/test/output/line_theme_keynote_.png +0 -0
  207. data/test/output/line_theme_keynote_400.png +0 -0
  208. data/test/output/line_theme_odeo_.png +0 -0
  209. data/test/output/line_theme_odeo_400.png +0 -0
  210. data/test/output/line_theme_rails_keynote_.png +0 -0
  211. data/test/output/line_theme_rails_keynote_400.png +0 -0
  212. data/test/output/line_tiny.png +0 -0
  213. data/test/output/line_transparent.png +0 -0
  214. data/test/output/line_very_small.png +0 -0
  215. data/test/output/line_wide_graph.png +0 -0
  216. data/test/output/line_wide_graph_small.png +0 -0
  217. data/test/output/line_xy.png +0 -0
  218. data/test/output/line_xy_pairs.png +0 -0
  219. data/test/output/line_y_axis_increment.png +0 -0
  220. data/test/output/mini_bar.png +0 -0
  221. data/test/output/mini_pie.png +0 -0
  222. data/test/output/mini_pie_right_legend.png +0 -0
  223. data/test/output/mini_side_bar.png +0 -0
  224. data/test/output/mini_side_bar_multi_color.png +0 -0
  225. data/test/output/net_dots_tiny.png +0 -0
  226. data/test/output/net_large.png +0 -0
  227. data/test/output/net_many.png +0 -0
  228. data/test/output/net_many_nets_small.png +0 -0
  229. data/test/output/net_no_data.png +0 -0
  230. data/test/output/net_no_data_msg.png +0 -0
  231. data/test/output/net_no_data_other.png +0 -0
  232. data/test/output/net_no_legend.png +0 -0
  233. data/test/output/net_no_net_markers.png +0 -0
  234. data/test/output/net_no_title.png +0 -0
  235. data/test/output/net_nothing_but_the_graph.png +0 -0
  236. data/test/output/net_similar_high_end_values.png +0 -0
  237. data/test/output/net_small.png +0 -0
  238. data/test/output/net_small_small.png +0 -0
  239. data/test/output/net_small_small_zero.png +0 -0
  240. data/test/output/net_small_zero.png +0 -0
  241. data/test/output/net_wide_graph.png +0 -0
  242. data/test/output/net_wide_graph_small.png +0 -0
  243. data/test/output/pie_equal.png +0 -0
  244. data/test/output/pie_grey.png +0 -0
  245. data/test/output/pie_keynote.png +0 -0
  246. data/test/output/pie_keynote_small.png +0 -0
  247. data/test/output/pie_legend.png +0 -0
  248. data/test/output/pie_legend_small.png +0 -0
  249. data/test/output/pie_nearly_equal.png +0 -0
  250. data/test/output/pie_one_val.png +0 -0
  251. data/test/output/pie_pastel.png +0 -0
  252. data/test/output/pie_simple.png +0 -0
  253. data/test/output/pie_wide.png +0 -0
  254. data/test/output/pie_wrapped_legend_16_400.png +0 -0
  255. data/test/output/pie_wrapped_legend_16_800.png +0 -0
  256. data/test/output/pie_wrapped_legend_30_400.png +0 -0
  257. data/test/output/pie_wrapped_legend_30_800.png +0 -0
  258. data/test/output/pie_wrapped_legend_4_400.png +0 -0
  259. data/test/output/pie_wrapped_legend_4_800.png +0 -0
  260. data/test/output/pie_wrapped_legend__400.png +0 -0
  261. data/test/output/pie_wrapped_legend__800.png +0 -0
  262. data/test/output/pie_zero.png +0 -0
  263. data/test/output/scatter_all_neg.png +0 -0
  264. data/test/output/scatter_all_neg_400.png +0 -0
  265. data/test/output/scatter_basic.png +0 -0
  266. data/test/output/scatter_many.png +0 -0
  267. data/test/output/scatter_no_data.png +0 -0
  268. data/test/output/scatter_no_data_msg.png +0 -0
  269. data/test/output/scatter_no_data_other.png +0 -0
  270. data/test/output/scatter_no_hide.png +0 -0
  271. data/test/output/scatter_no_labels.png +0 -0
  272. data/test/output/scatter_no_legend.png +0 -0
  273. data/test/output/scatter_no_line_markers.png +0 -0
  274. data/test/output/scatter_no_title.png +0 -0
  275. data/test/output/scatter_nothing_but_the_graph.png +0 -0
  276. data/test/output/scatter_pos_neg.png +0 -0
  277. data/test/output/scatter_pos_neg_400.png +0 -0
  278. data/test/output/scatter_wide_graph.png +0 -0
  279. data/test/output/scatter_wide_graph_small.png +0 -0
  280. data/test/output/scene_hazy_night.png +0 -0
  281. data/test/output/scene_not_hazy_day.png +0 -0
  282. data/test/output/scene_partly_cloudy_day.png +0 -0
  283. data/test/output/scene_stormy_day.png +0 -0
  284. data/test/output/scene_stormy_night.png +0 -0
  285. data/test/output/side_bar.png +0 -0
  286. data/test/output/side_bar_data_range.png +0 -0
  287. data/test/output/side_bar_labels.png +0 -0
  288. data/test/output/side_bar_spacing_full.png +0 -0
  289. data/test/output/side_bar_spacing_half.png +0 -0
  290. data/test/output/side_bar_spacing_none.png +0 -0
  291. data/test/output/side_stacked_bar_keynote.png +0 -0
  292. data/test/output/side_stacked_bar_keynote_small.png +0 -0
  293. data/test/output/side_stacked_bar_labels.png +0 -0
  294. data/test/output/side_stacked_bar_long_label.png +0 -0
  295. data/test/output/side_stacked_bar_wide.png +0 -0
  296. data/test/output/spider_37signals.png +0 -0
  297. data/test/output/spider_equal.png +0 -0
  298. data/test/output/spider_keynote.png +0 -0
  299. data/test/output/spider_legend.png +0 -0
  300. data/test/output/spider_legend_small.png +0 -0
  301. data/test/output/spider_lots_of_data.png +0 -0
  302. data/test/output/spider_lots_of_data_normal_names.png +0 -0
  303. data/test/output/spider_nearly_equal.png +0 -0
  304. data/test/output/spider_no_axes.png +0 -0
  305. data/test/output/spider_no_background.png +0 -0
  306. data/test/output/spider_no_print.png +0 -0
  307. data/test/output/spider_overlay_1.png +0 -0
  308. data/test/output/spider_overlay_2.png +0 -0
  309. data/test/output/spider_rotation.png +0 -0
  310. data/test/output/spider_small.png +0 -0
  311. data/test/output/spider_wide.png +0 -0
  312. data/test/output/spider_zero.png +0 -0
  313. data/test/output/stacked_area_keynote.png +0 -0
  314. data/test/output/stacked_area_keynote_small.png +0 -0
  315. data/test/output/stacked_bar_keynote.png +0 -0
  316. data/test/output/stacked_bar_keynote_small.png +0 -0
  317. data/test/output_java/accum_bar.png +0 -0
  318. data/test/output_java/area_keynote.png +0 -0
  319. data/test/output_java/area_keynote_small.png +0 -0
  320. data/test/output_java/area_many.png +0 -0
  321. data/test/output_java/area_many_areas_small.png +0 -0
  322. data/test/output_java/area_tiny.png +0 -0
  323. data/test/output_java/area_wide.png +0 -0
  324. data/test/output_java/bar2_wrapped_legend_16_400.png +0 -0
  325. data/test/output_java/bar2_wrapped_legend_16_800.png +0 -0
  326. data/test/output_java/bar2_wrapped_legend_30_400.png +0 -0
  327. data/test/output_java/bar2_wrapped_legend_30_800.png +0 -0
  328. data/test/output_java/bar2_wrapped_legend_4_400.png +0 -0
  329. data/test/output_java/bar2_wrapped_legend_4_800.png +0 -0
  330. data/test/output_java/bar2_wrapped_legend__400.png +0 -0
  331. data/test/output_java/bar2_wrapped_legend__800.png +0 -0
  332. data/test/output_java/bar_background_gradient_bottom_top.png +0 -0
  333. data/test/output_java/bar_background_gradient_left_right.png +0 -0
  334. data/test/output_java/bar_background_gradient_right_left.png +0 -0
  335. data/test/output_java/bar_background_gradient_top_bottom.png +0 -0
  336. data/test/output_java/bar_background_gradient_topleft_bottomright.png +0 -0
  337. data/test/output_java/bar_background_gradient_topright_bottomleft.png +0 -0
  338. data/test/output_java/bar_formatted_numbers.png +0 -0
  339. data/test/output_java/bar_keynote.png +0 -0
  340. data/test/output_java/bar_keynote_small.png +0 -0
  341. data/test/output_java/bar_long_legend_text.png +0 -0
  342. data/test/output_java/bar_manual_colors.png +0 -0
  343. data/test/output_java/bar_marker_shadow.png +0 -0
  344. data/test/output_java/bar_nearly_zero_max_10.png +0 -0
  345. data/test/output_java/bar_no_legend.png +0 -0
  346. data/test/output_java/bar_no_line_markers.png +0 -0
  347. data/test/output_java/bar_no_title.png +0 -0
  348. data/test/output_java/bar_no_title_or_legend.png +0 -0
  349. data/test/output_java/bar_odeo.png +0 -0
  350. data/test/output_java/bar_one_value.png +0 -0
  351. data/test/output_java/bar_pos_neg.png +0 -0
  352. data/test/output_java/bar_rails_keynote.png +0 -0
  353. data/test/output_java/bar_set_absolute_trunc.png +0 -0
  354. data/test/output_java/bar_set_label_stagger_height.png +0 -0
  355. data/test/output_java/bar_set_legend_box_size_lg.png +0 -0
  356. data/test/output_java/bar_set_legend_box_size_sm.png +0 -0
  357. data/test/output_java/bar_set_marker.png +0 -0
  358. data/test/output_java/bar_set_trailing_dots_trunc.png +0 -0
  359. data/test/output_java/bar_spacing_full.png +0 -0
  360. data/test/output_java/bar_spacing_half.png +0 -0
  361. data/test/output_java/bar_spacing_none.png +0 -0
  362. data/test/output_java/bar_tall_graph.png +0 -0
  363. data/test/output_java/bar_tall_graph_small.png +0 -0
  364. data/test/output_java/bar_themed.png +0 -0
  365. data/test/output_java/bar_title_margin.png +0 -0
  366. data/test/output_java/bar_value_labels.png +0 -0
  367. data/test/output_java/bar_wide_graph.png +0 -0
  368. data/test/output_java/bar_wide_graph_small.png +0 -0
  369. data/test/output_java/bar_wrapped_legend_16_400.png +0 -0
  370. data/test/output_java/bar_wrapped_legend_16_800.png +0 -0
  371. data/test/output_java/bar_wrapped_legend_30_400.png +0 -0
  372. data/test/output_java/bar_wrapped_legend_30_800.png +0 -0
  373. data/test/output_java/bar_wrapped_legend_4_400.png +0 -0
  374. data/test/output_java/bar_wrapped_legend_4_800.png +0 -0
  375. data/test/output_java/bar_wrapped_legend__400.png +0 -0
  376. data/test/output_java/bar_wrapped_legend__800.png +0 -0
  377. data/test/output_java/bar_x_y_labels.png +0 -0
  378. data/test/output_java/bar_y_increment_1.png +0 -0
  379. data/test/output_java/bar_y_increment_2.0.png +0 -0
  380. data/test/output_java/bar_y_increment_20.png +0 -0
  381. data/test/output_java/bar_y_increment_5.png +0 -0
  382. data/test/output_java/bar_zero_marker_count.png +0 -0
  383. data/test/output_java/bar_zero_spacing.png +0 -0
  384. data/test/output_java/bezier.png +0 -0
  385. data/test/output_java/bezier_2.png +0 -0
  386. data/test/output_java/bezier_3.png +0 -0
  387. data/test/output_java/bullet_greyscale.png +0 -0
  388. data/test/output_java/bullet_no_options.png +0 -0
  389. data/test/output_java/dot.png +0 -0
  390. data/test/output_java/dot_manual_colors.png +0 -0
  391. data/test/output_java/dot_nearly_zero_max_10.png +0 -0
  392. data/test/output_java/dot_no_legend.png +0 -0
  393. data/test/output_java/dot_no_line_markers.png +0 -0
  394. data/test/output_java/dot_no_title.png +0 -0
  395. data/test/output_java/dot_no_title_or_legend.png +0 -0
  396. data/test/output_java/dot_one_value.png +0 -0
  397. data/test/output_java/dot_pos_neg.png +0 -0
  398. data/test/output_java/dot_set_legend_box_size_lg.png +0 -0
  399. data/test/output_java/dot_set_legend_box_size_sm.png +0 -0
  400. data/test/output_java/dot_set_marker.png +0 -0
  401. data/test/output_java/dot_small.png +0 -0
  402. data/test/output_java/dot_tall_graph.png +0 -0
  403. data/test/output_java/dot_tall_graph_small.png +0 -0
  404. data/test/output_java/dot_themed.png +0 -0
  405. data/test/output_java/dot_wide_graph.png +0 -0
  406. data/test/output_java/dot_wide_graph_small.png +0 -0
  407. data/test/output_java/dot_x_y_labels.png +0 -0
  408. data/test/output_java/dot_y_increment_1.png +0 -0
  409. data/test/output_java/dot_y_increment_2.0.png +0 -0
  410. data/test/output_java/dot_y_increment_20.png +0 -0
  411. data/test/output_java/dot_y_increment_5.png +0 -0
  412. data/test/output_java/enhancements-1.png +0 -0
  413. data/test/output_java/enhancements.png +0 -0
  414. data/test/output_java/line_all_neg.png +0 -0
  415. data/test/output_java/line_all_neg_400.png +0 -0
  416. data/test/output_java/line_all_neg_no_max.png +0 -0
  417. data/test/output_java/line_baseline.png +0 -0
  418. data/test/output_java/line_hang_value.png +0 -0
  419. data/test/output_java/line_hide_dots.png +0 -0
  420. data/test/output_java/line_hide_lines.png +0 -0
  421. data/test/output_java/line_jruby_error.png +0 -0
  422. data/test/output_java/line_large.png +0 -0
  423. data/test/output_java/line_large_baseline.png +0 -0
  424. data/test/output_java/line_legend_at_bottom.png +0 -0
  425. data/test/output_java/line_many.png +0 -0
  426. data/test/output_java/line_many_lines_small.png +0 -0
  427. data/test/output_java/line_many_numbers.png +0 -0
  428. data/test/output_java/line_marker_label_accuracy.png +0 -0
  429. data/test/output_java/line_more_sets_than_colors.png +0 -0
  430. data/test/output_java/line_no_data.png +0 -0
  431. data/test/output_java/line_no_data_msg.png +0 -0
  432. data/test/output_java/line_no_data_other.png +0 -0
  433. data/test/output_java/line_no_hide.png +0 -0
  434. data/test/output_java/line_no_legend.png +0 -0
  435. data/test/output_java/line_no_line_markers.png +0 -0
  436. data/test/output_java/line_no_title.png +0 -0
  437. data/test/output_java/line_nothing_but_the_graph.png +0 -0
  438. data/test/output_java/line_one_value.png +0 -0
  439. data/test/output_java/line_one_value_array.png +0 -0
  440. data/test/output_java/line_pos_neg.png +0 -0
  441. data/test/output_java/line_pos_neg_400.png +0 -0
  442. data/test/output_java/line_similar_high_end_values.png +0 -0
  443. data/test/output_java/line_similar_high_end_values_with_floor.png +0 -0
  444. data/test/output_java/line_small_small_zero.png +0 -0
  445. data/test/output_java/line_small_values.png +0 -0
  446. data/test/output_java/line_small_values_small.png +0 -0
  447. data/test/output_java/line_small_zero.png +0 -0
  448. data/test/output_java/line_some_nil_points.png +0 -0
  449. data/test/output_java/line_theme_37signals_.png +0 -0
  450. data/test/output_java/line_theme_37signals_400.png +0 -0
  451. data/test/output_java/line_theme_keynote_.png +0 -0
  452. data/test/output_java/line_theme_keynote_400.png +0 -0
  453. data/test/output_java/line_theme_odeo_.png +0 -0
  454. data/test/output_java/line_theme_odeo_400.png +0 -0
  455. data/test/output_java/line_theme_rails_keynote_.png +0 -0
  456. data/test/output_java/line_theme_rails_keynote_400.png +0 -0
  457. data/test/output_java/line_tiny.png +0 -0
  458. data/test/output_java/line_transparent.png +0 -0
  459. data/test/output_java/line_very_small.png +0 -0
  460. data/test/output_java/line_wide_graph.png +0 -0
  461. data/test/output_java/line_wide_graph_small.png +0 -0
  462. data/test/output_java/line_xy.png +0 -0
  463. data/test/output_java/line_xy_pairs.png +0 -0
  464. data/test/output_java/line_y_axis_increment.png +0 -0
  465. data/test/output_java/mini_bar.png +0 -0
  466. data/test/output_java/mini_pie.png +0 -0
  467. data/test/output_java/mini_pie_right_legend.png +0 -0
  468. data/test/output_java/mini_side_bar.png +0 -0
  469. data/test/output_java/mini_side_bar_multi_color.png +0 -0
  470. data/test/output_java/net_dots_tiny.png +0 -0
  471. data/test/output_java/net_large.png +0 -0
  472. data/test/output_java/net_many.png +0 -0
  473. data/test/output_java/net_many_nets_small.png +0 -0
  474. data/test/output_java/net_no_data.png +0 -0
  475. data/test/output_java/net_no_data_msg.png +0 -0
  476. data/test/output_java/net_no_data_other.png +0 -0
  477. data/test/output_java/net_no_legend.png +0 -0
  478. data/test/output_java/net_no_net_markers.png +0 -0
  479. data/test/output_java/net_no_title.png +0 -0
  480. data/test/output_java/net_nothing_but_the_graph.png +0 -0
  481. data/test/output_java/net_similar_high_end_values.png +0 -0
  482. data/test/output_java/net_small.png +0 -0
  483. data/test/output_java/net_small_small.png +0 -0
  484. data/test/output_java/net_small_small_zero.png +0 -0
  485. data/test/output_java/net_small_zero.png +0 -0
  486. data/test/output_java/net_wide_graph.png +0 -0
  487. data/test/output_java/net_wide_graph_small.png +0 -0
  488. data/test/output_java/pie_equal.png +0 -0
  489. data/test/output_java/pie_grey.png +0 -0
  490. data/test/output_java/pie_keynote.png +0 -0
  491. data/test/output_java/pie_keynote_small.png +0 -0
  492. data/test/output_java/pie_legend.png +0 -0
  493. data/test/output_java/pie_legend_small.png +0 -0
  494. data/test/output_java/pie_nearly_equal.png +0 -0
  495. data/test/output_java/pie_one_val.png +0 -0
  496. data/test/output_java/pie_pastel.png +0 -0
  497. data/test/output_java/pie_simple.png +0 -0
  498. data/test/output_java/pie_wide.png +0 -0
  499. data/test/output_java/pie_wrapped_legend_16_400.png +0 -0
  500. data/test/output_java/pie_wrapped_legend_16_800.png +0 -0
  501. data/test/output_java/pie_wrapped_legend_30_400.png +0 -0
  502. data/test/output_java/pie_wrapped_legend_30_800.png +0 -0
  503. data/test/output_java/pie_wrapped_legend_4_400.png +0 -0
  504. data/test/output_java/pie_wrapped_legend_4_800.png +0 -0
  505. data/test/output_java/pie_wrapped_legend__400.png +0 -0
  506. data/test/output_java/pie_wrapped_legend__800.png +0 -0
  507. data/test/output_java/pie_zero.png +0 -0
  508. data/test/output_java/scatter_all_neg.png +0 -0
  509. data/test/output_java/scatter_all_neg_400.png +0 -0
  510. data/test/output_java/scatter_basic.png +0 -0
  511. data/test/output_java/scatter_many.png +0 -0
  512. data/test/output_java/scatter_no_data.png +0 -0
  513. data/test/output_java/scatter_no_data_msg.png +0 -0
  514. data/test/output_java/scatter_no_data_other.png +0 -0
  515. data/test/output_java/scatter_no_hide.png +0 -0
  516. data/test/output_java/scatter_no_labels.png +0 -0
  517. data/test/output_java/scatter_no_legend.png +0 -0
  518. data/test/output_java/scatter_no_line_markers.png +0 -0
  519. data/test/output_java/scatter_no_title.png +0 -0
  520. data/test/output_java/scatter_nothing_but_the_graph.png +0 -0
  521. data/test/output_java/scatter_pos_neg.png +0 -0
  522. data/test/output_java/scatter_pos_neg_400.png +0 -0
  523. data/test/output_java/scatter_wide_graph.png +0 -0
  524. data/test/output_java/scatter_wide_graph_small.png +0 -0
  525. data/test/output_java/scene_hazy_night.png +0 -0
  526. data/test/output_java/scene_not_hazy_day.png +0 -0
  527. data/test/output_java/scene_partly_cloudy_day.png +0 -0
  528. data/test/output_java/scene_stormy_day.png +0 -0
  529. data/test/output_java/scene_stormy_night.png +0 -0
  530. data/test/output_java/side_bar.png +0 -0
  531. data/test/output_java/side_bar_data_range.png +0 -0
  532. data/test/output_java/side_bar_labels.png +0 -0
  533. data/test/output_java/side_bar_spacing_full.png +0 -0
  534. data/test/output_java/side_bar_spacing_half.png +0 -0
  535. data/test/output_java/side_bar_spacing_none.png +0 -0
  536. data/test/output_java/side_stacked_bar_keynote.png +0 -0
  537. data/test/output_java/side_stacked_bar_keynote_small.png +0 -0
  538. data/test/output_java/side_stacked_bar_labels.png +0 -0
  539. data/test/output_java/side_stacked_bar_long_label.png +0 -0
  540. data/test/output_java/side_stacked_bar_wide.png +0 -0
  541. data/test/output_java/spider_37signals.png +0 -0
  542. data/test/output_java/spider_equal.png +0 -0
  543. data/test/output_java/spider_keynote.png +0 -0
  544. data/test/output_java/spider_legend.png +0 -0
  545. data/test/output_java/spider_legend_small.png +0 -0
  546. data/test/output_java/spider_lots_of_data.png +0 -0
  547. data/test/output_java/spider_lots_of_data_normal_names.png +0 -0
  548. data/test/output_java/spider_nearly_equal.png +0 -0
  549. data/test/output_java/spider_no_axes.png +0 -0
  550. data/test/output_java/spider_no_background.png +0 -0
  551. data/test/output_java/spider_no_print.png +0 -0
  552. data/test/output_java/spider_overlay_1.png +0 -0
  553. data/test/output_java/spider_overlay_2.png +0 -0
  554. data/test/output_java/spider_rotation.png +0 -0
  555. data/test/output_java/spider_small.png +0 -0
  556. data/test/output_java/spider_wide.png +0 -0
  557. data/test/output_java/spider_zero.png +0 -0
  558. data/test/output_java/stacked_area_keynote.png +0 -0
  559. data/test/output_java/stacked_area_keynote_small.png +0 -0
  560. data/test/output_java/stacked_bar_keynote.png +0 -0
  561. data/test/output_java/stacked_bar_keynote_small.png +0 -0
  562. data/test/test_accumulator_bar.rb +51 -0
  563. data/test/test_area.rb +134 -0
  564. data/test/test_bar.rb +505 -0
  565. data/test/test_base.rb +8 -0
  566. data/test/test_bezier.rb +33 -0
  567. data/test/test_bullet.rb +26 -0
  568. data/test/test_dot.rb +263 -0
  569. data/test/test_legend.rb +68 -0
  570. data/test/test_line.rb +626 -0
  571. data/test/test_mini_bar.rb +33 -0
  572. data/test/test_mini_pie.rb +25 -0
  573. data/test/test_mini_side_bar.rb +36 -0
  574. data/test/test_net.rb +231 -0
  575. data/test/test_photo.rb +41 -0
  576. data/test/test_pie.rb +154 -0
  577. data/test/test_scatter.rb +233 -0
  578. data/test/test_scene.rb +100 -0
  579. data/test/test_side_bar.rb +56 -0
  580. data/test/test_sidestacked_bar.rb +105 -0
  581. data/test/test_spider.rb +226 -0
  582. data/test/test_stacked_area.rb +52 -0
  583. data/test/test_stacked_bar.rb +52 -0
  584. metadata +1176 -0
@@ -0,0 +1,51 @@
1
+
2
+ require File.dirname(__FILE__) + '/base'
3
+
4
+ class Gruff::Area < Gruff::Base
5
+ def initialize(*)
6
+ super
7
+ @sorted_drawing = true
8
+ end
9
+
10
+ def draw
11
+ super
12
+
13
+ return unless @has_data
14
+
15
+ @x_increment = @graph_width / (@column_count - 1).to_f
16
+ @d = @d.stroke 'transparent'
17
+
18
+ @norm_data.each do |data_row|
19
+ poly_points = Array.new
20
+ prev_x = prev_y = 0.0
21
+ @d = @d.fill data_row[DATA_COLOR_INDEX]
22
+
23
+ data_row[DATA_VALUES_INDEX].each_with_index do |data_point, index|
24
+ # Use incremented x and scaled y
25
+ new_x = @graph_left + (@x_increment * index)
26
+ new_y = @graph_top + (@graph_height - data_point * @graph_height)
27
+
28
+ poly_points << new_x
29
+ poly_points << new_y
30
+
31
+ draw_label(new_x, index)
32
+
33
+ prev_x = new_x
34
+ prev_y = new_y
35
+ end
36
+
37
+ # Add closing points, draw polygon
38
+ poly_points << @graph_right
39
+ poly_points << @graph_bottom - 1
40
+ poly_points << @graph_left
41
+ poly_points << @graph_bottom - 1
42
+
43
+ @d = @d.polyline(*poly_points)
44
+
45
+ end
46
+
47
+ @d.draw(@base_image)
48
+ end
49
+
50
+
51
+ end
@@ -0,0 +1,108 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+ require File.dirname(__FILE__) + '/bar_conversion'
3
+
4
+ class Gruff::Bar < Gruff::Base
5
+
6
+ # Spacing factor applied between bars
7
+ attr_accessor :bar_spacing
8
+
9
+ def initialize(*args)
10
+ super
11
+ @spacing_factor = 0.9
12
+ end
13
+
14
+ def draw
15
+ # Labels will be centered over the left of the bar if
16
+ # there are more labels than columns. This is basically the same
17
+ # as where it would be for a line graph.
18
+ @center_labels_over_point = (@labels.keys.length > @column_count ? true : false)
19
+
20
+ super
21
+ return unless @has_data
22
+
23
+ draw_bars
24
+ end
25
+
26
+ # Can be used to adjust the spaces between the bars.
27
+ # Accepts values between 0.00 and 1.00 where 0.00 means no spacing at all
28
+ # and 1 means that each bars' width is nearly 0 (so each bar is a simple
29
+ # line with no x dimension).
30
+ #
31
+ # Default value is 0.9.
32
+ def spacing_factor=(space_percent)
33
+ raise ArgumentError, 'spacing_factor must be between 0.00 and 1.00' unless (space_percent >= 0 and space_percent <= 1)
34
+ @spacing_factor = (1 - space_percent)
35
+ end
36
+
37
+ protected
38
+
39
+ def draw_bars
40
+ # Setup spacing.
41
+ #
42
+ # Columns sit side-by-side.
43
+ @bar_spacing ||= @spacing_factor # space between the bars
44
+ @bar_width = @graph_width / (@column_count * @data.length).to_f
45
+ padding = (@bar_width * (1 - @bar_spacing)) / 2
46
+
47
+ @d = @d.stroke_opacity 0.0
48
+
49
+ # Setup the BarConversion Object
50
+ conversion = Gruff::BarConversion.new()
51
+ conversion.graph_height = @graph_height
52
+ conversion.graph_top = @graph_top
53
+
54
+ # Set up the right mode [1,2,3] see BarConversion for further explanation
55
+ if @minimum_value >= 0 then
56
+ # all bars go from zero to positiv
57
+ conversion.mode = 1
58
+ else
59
+ # all bars go from 0 to negativ
60
+ if @maximum_value <= 0 then
61
+ conversion.mode = 2
62
+ else
63
+ # bars either go from zero to negativ or to positiv
64
+ conversion.mode = 3
65
+ conversion.spread = @spread
66
+ conversion.minimum_value = @minimum_value
67
+ conversion.zero = -@minimum_value/@spread
68
+ end
69
+ end
70
+
71
+ # iterate over all normalised data
72
+ @norm_data.each_with_index do |data_row, row_index|
73
+
74
+ data_row[DATA_VALUES_INDEX].each_with_index do |data_point, point_index|
75
+ # Use incremented x and scaled y
76
+ # x
77
+ left_x = @graph_left + (@bar_width * (row_index + point_index + ((@data.length - 1) * point_index))) + padding
78
+ right_x = left_x + @bar_width * @bar_spacing
79
+ # y
80
+ conv = []
81
+ conversion.get_left_y_right_y_scaled( data_point, conv )
82
+
83
+ # create new bar
84
+ @d = @d.fill data_row[DATA_COLOR_INDEX]
85
+ @d = @d.rectangle(left_x, conv[0], right_x, conv[1])
86
+
87
+ # Calculate center based on bar_width and current row
88
+ label_center = @graph_left +
89
+ (@data.length * @bar_width * point_index) +
90
+ (@data.length * @bar_width / 2.0)
91
+
92
+ # Subtract half a bar width to center left if requested
93
+ draw_label(label_center - (@center_labels_over_point ? @bar_width / 2.0 : 0.0), point_index)
94
+ if @show_labels_for_bar_values
95
+ val = (@label_formatting || '%.2f') % @norm_data[row_index][3][point_index]
96
+ draw_value_label(left_x + (right_x - left_x)/2, conv[0]-30, val.commify, true)
97
+ end
98
+ end
99
+
100
+ end
101
+
102
+ # Draw the last label if requested
103
+ draw_label(@graph_right, @column_count) if @center_labels_over_point
104
+
105
+ @d.draw(@base_image)
106
+ end
107
+
108
+ end
@@ -0,0 +1,46 @@
1
+ ##
2
+ # Original Author: David Stokar
3
+ #
4
+ # This class perfoms the y coordinats conversion for the bar class.
5
+ #
6
+ # There are three cases:
7
+ #
8
+ # 1. Bars all go from zero in positive direction
9
+ # 2. Bars all go from zero to negative direction
10
+ # 3. Bars either go from zero to positive or from zero to negative
11
+ #
12
+ class Gruff::BarConversion
13
+ attr_writer :mode
14
+ attr_writer :zero
15
+ attr_writer :graph_top
16
+ attr_writer :graph_height
17
+ attr_writer :minimum_value
18
+ attr_writer :spread
19
+
20
+ def get_left_y_right_y_scaled(data_point, result)
21
+ case @mode
22
+ when 1 then # Case one
23
+ # minimum value >= 0 ( only positiv values )
24
+ result[0] = @graph_top + @graph_height*(1 - data_point) + 1
25
+ result[1] = @graph_top + @graph_height - 1
26
+ when 2 then # Case two
27
+ # only negativ values
28
+ result[0] = @graph_top + 1
29
+ result[1] = @graph_top + @graph_height*(1 - data_point) - 1
30
+ when 3 then # Case three
31
+ # positiv and negativ values
32
+ val = data_point-@minimum_value/@spread
33
+ if data_point >= @zero
34
+ result[0] = @graph_top + @graph_height*(1 - (val-@zero)) + 1
35
+ result[1] = @graph_top + @graph_height*(1 - @zero) - 1
36
+ else
37
+ result[0] = @graph_top + @graph_height*(1 - (val-@zero)) + 1
38
+ result[1] = @graph_top + @graph_height*(1 - @zero) - 1
39
+ end
40
+ else
41
+ result[0] = 0.0
42
+ result[1] = 0.0
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,1169 @@
1
+ require 'rubygems'
2
+ require 'RMagick'
3
+ require 'bigdecimal'
4
+
5
+ require File.dirname(__FILE__) + '/deprecated'
6
+
7
+ ##
8
+ # = Gruff. Graphs.
9
+ #
10
+ # Author:: Geoffrey Grosenbach boss@topfunky.com
11
+ #
12
+ # Originally Created:: October 23, 2005
13
+ #
14
+ # Extra thanks to Tim Hunter for writing RMagick, and also contributions by
15
+ # Jarkko Laine, Mike Perham, Andreas Schwarz, Alun Eyre, Guillaume Theoret,
16
+ # David Stokar, Paul Rogers, Dave Woodward, Frank Oxener, Kevin Clark, Cies
17
+ # Breijs, Richard Cowin, and a cast of thousands.
18
+ #
19
+ # See Gruff::Base#theme= for setting themes.
20
+
21
+ module Gruff
22
+ class Base
23
+
24
+ include Magick
25
+ include Deprecated
26
+
27
+ # Draw extra lines showing where the margins and text centers are
28
+ DEBUG = false
29
+
30
+ # Used for navigating the array of data to plot
31
+ DATA_LABEL_INDEX = 0
32
+ DATA_VALUES_INDEX = 1
33
+ DATA_COLOR_INDEX = 2
34
+ DATA_VALUES_X_INDEX = 3
35
+
36
+ # Space around text elements. Mostly used for vertical spacing
37
+ LEGEND_MARGIN = TITLE_MARGIN = 20.0
38
+ LABEL_MARGIN = 10.0
39
+ DEFAULT_MARGIN = 20.0
40
+
41
+ DEFAULT_TARGET_WIDTH = 800
42
+
43
+ THOUSAND_SEPARATOR = ','
44
+
45
+ # Blank space above the graph
46
+ attr_accessor :top_margin
47
+
48
+ # Blank space below the graph
49
+ attr_accessor :bottom_margin
50
+
51
+ # Blank space to the right of the graph
52
+ attr_accessor :right_margin
53
+
54
+ # Blank space to the left of the graph
55
+ attr_accessor :left_margin
56
+
57
+ # Blank space below the title
58
+ attr_accessor :title_margin
59
+
60
+ # Blank space below the legend
61
+ attr_accessor :legend_margin
62
+
63
+ # A hash of names for the individual columns, where the key is the array
64
+ # index for the column this label represents.
65
+ #
66
+ # Not all columns need to be named.
67
+ #
68
+ # Example: 0 => 2005, 3 => 2006, 5 => 2007, 7 => 2008
69
+ attr_accessor :labels
70
+
71
+ # Used internally for spacing.
72
+ #
73
+ # By default, labels are centered over the point they represent.
74
+ attr_accessor :center_labels_over_point
75
+
76
+ # Used internally for horizontal graph types.
77
+ attr_accessor :has_left_labels
78
+
79
+ # A label for the bottom of the graph
80
+ attr_accessor :x_axis_label
81
+
82
+ # A label for the left side of the graph
83
+ attr_accessor :y_axis_label
84
+
85
+ # attr_accessor :x_axis_increment
86
+
87
+ # Manually set increment of the horizontal marking lines
88
+ attr_accessor :y_axis_increment
89
+
90
+ # Height of staggering between labels (Bar graph only)
91
+ attr_accessor :label_stagger_height
92
+
93
+ # Truncates labels if longer than max specified
94
+ attr_accessor :label_max_size
95
+
96
+ # How truncated labels visually appear if they exceed label_max_size
97
+ # :absolute - does not show trailing dots to indicate truncation. This is
98
+ # the default.
99
+ # :trailing_dots - shows trailing dots to indicate truncation (note
100
+ # that label_max_size must be greater than 3).
101
+ attr_accessor :label_truncation_style
102
+
103
+ # Get or set the list of colors that will be used to draw the bars or lines.
104
+ attr_accessor :colors
105
+
106
+ # The large title of the graph displayed at the top
107
+ attr_accessor :title
108
+
109
+ # Font used for titles, labels, etc. Works best if you provide the full
110
+ # path to the TTF font file. RMagick must be built with the Freetype
111
+ # libraries for this to work properly.
112
+ #
113
+ # Tries to find Bitstream Vera (Vera.ttf) in the location specified by
114
+ # ENV['MAGICK_FONT_PATH']. Uses default RMagick font otherwise.
115
+ #
116
+ # The font= method below fulfills the role of the writer, so we only need
117
+ # a reader here.
118
+ attr_reader :font
119
+
120
+ attr_accessor :font_color
121
+
122
+ # Prevent drawing of line markers
123
+ attr_accessor :hide_line_markers
124
+
125
+ # Prevent drawing of the legend
126
+ attr_accessor :hide_legend
127
+
128
+ # Prevent drawing of the title
129
+ attr_accessor :hide_title
130
+
131
+ # Prevent drawing of line numbers
132
+ attr_accessor :hide_line_numbers
133
+
134
+ # Message shown when there is no data. Fits up to 20 characters. Defaults
135
+ # to "No Data."
136
+ attr_accessor :no_data_message
137
+
138
+ # The font size of the large title at the top of the graph
139
+ attr_accessor :title_font_size
140
+
141
+ # Optionally set the size of the font. Based on an 800x600px graph.
142
+ # Default is 20.
143
+ #
144
+ # Will be scaled down if the graph is smaller than 800px wide.
145
+ attr_accessor :legend_font_size
146
+
147
+ # Display the legend under the graph
148
+ attr_accessor :legend_at_bottom
149
+
150
+ # The font size of the labels around the graph
151
+ attr_accessor :marker_font_size
152
+
153
+ # The color of the auxiliary lines
154
+ attr_accessor :marker_color
155
+ attr_accessor :marker_shadow_color
156
+
157
+ # The number of horizontal lines shown for reference
158
+ attr_accessor :marker_count
159
+
160
+ # You can manually set a minimum value instead of having the values
161
+ # guessed for you.
162
+ #
163
+ # Set it after you have given all your data to the graph object.
164
+ attr_accessor :minimum_value
165
+
166
+ # You can manually set a maximum value, such as a percentage-based graph
167
+ # that always goes to 100.
168
+ #
169
+ # If you use this, you must set it after you have given all your data to
170
+ # the graph object.
171
+ attr_accessor :maximum_value
172
+
173
+ # Set to true if you want the data sets sorted with largest avg values drawn
174
+ # first.
175
+ attr_accessor :sort
176
+
177
+ # Set to true if you want the data sets drawn with largest avg values drawn
178
+ # first. This does not affect the legend.
179
+ attr_accessor :sorted_drawing
180
+
181
+ # Experimental
182
+ attr_accessor :additional_line_values
183
+
184
+ # Experimental
185
+ attr_accessor :stacked
186
+
187
+ # Optionally set the size of the colored box by each item in the legend.
188
+ # Default is 20.0
189
+ #
190
+ # Will be scaled down if graph is smaller than 800px wide.
191
+ attr_accessor :legend_box_size
192
+
193
+ # Output the values for the bars on a bar graph
194
+ # Default is false
195
+ attr_accessor :show_labels_for_bar_values
196
+
197
+ # Set the number output format for labels using sprintf
198
+ # Default is "%.2f"
199
+ attr_accessor :label_formatting
200
+
201
+ # With Side Bars use the data label for the marker value to the left of the bar
202
+ # Default is false
203
+ attr_accessor :use_data_label
204
+ # If one numerical argument is given, the graph is drawn at 4/3 ratio
205
+ # according to the given width (800 results in 800x600, 400 gives 400x300,
206
+ # etc.).
207
+ #
208
+ # Or, send a geometry string for other ratios ('800x400', '400x225').
209
+ #
210
+ # Looks for Bitstream Vera as the default font. Expects an environment var
211
+ # of MAGICK_FONT_PATH to be set. (Uses RMagick's default font otherwise.)
212
+ def initialize(target_width=DEFAULT_TARGET_WIDTH)
213
+ if Numeric === target_width
214
+ @columns = target_width.to_f
215
+ @rows = target_width.to_f * 0.75
216
+ else
217
+ geometric_width, geometric_height = target_width.split('x')
218
+ @columns = geometric_width.to_f
219
+ @rows = geometric_height.to_f
220
+ end
221
+
222
+ initialize_ivars
223
+
224
+ reset_themes
225
+ self.theme = Themes::KEYNOTE
226
+ end
227
+
228
+ # Set instance variables for this object.
229
+ #
230
+ # Subclasses can override this, call super, then set values separately.
231
+ #
232
+ # This makes it possible to set defaults in a subclass but still allow
233
+ # developers to change this values in their program.
234
+ def initialize_ivars
235
+ # Internal for calculations
236
+ @raw_columns = 800.0
237
+ @raw_rows = 800.0 * (@rows/@columns)
238
+ @column_count = 0
239
+ @marker_count = nil
240
+ @maximum_value = @minimum_value = nil
241
+ @has_data = false
242
+ @data = Array.new
243
+ @labels = Hash.new
244
+ @labels_seen = Hash.new
245
+ @sort = false
246
+ @title = nil
247
+
248
+ @scale = @columns / @raw_columns
249
+
250
+ vera_font_path = File.expand_path('Vera.ttf', ENV['MAGICK_FONT_PATH'])
251
+ @font = File.exists?(vera_font_path) ? vera_font_path : nil
252
+
253
+ @marker_font_size = 21.0
254
+ @legend_font_size = 20.0
255
+ @title_font_size = 36.0
256
+
257
+ @top_margin = @bottom_margin = @left_margin = @right_margin = DEFAULT_MARGIN
258
+ @legend_margin = LEGEND_MARGIN
259
+ @title_margin = TITLE_MARGIN
260
+
261
+ @legend_box_size = 20.0
262
+
263
+ @no_data_message = 'No Data'
264
+
265
+ @hide_line_markers = @hide_legend = @hide_title = @hide_line_numbers = @legend_at_bottom = @show_labels_for_bar_values = false
266
+ @center_labels_over_point = true
267
+ @has_left_labels = false
268
+ @label_stagger_height = 0
269
+ @label_max_size = 0
270
+ @label_truncation_style = :absolute
271
+
272
+ @additional_line_values = []
273
+ @additional_line_colors = []
274
+ @theme_options = {}
275
+
276
+ @x_axis_label = @y_axis_label = nil
277
+ @y_axis_increment = nil
278
+ @stacked = nil
279
+ @norm_data = nil
280
+ end
281
+
282
+ # Sets the top, bottom, left and right margins to +margin+.
283
+ def margins=(margin)
284
+ @top_margin = @left_margin = @right_margin = @bottom_margin = margin
285
+ end
286
+
287
+ # Sets the font for graph text to the font at +font_path+.
288
+ def font=(font_path)
289
+ @font = font_path
290
+ @d.font = @font
291
+ end
292
+
293
+ # Add a color to the list of available colors for lines.
294
+ #
295
+ # Example:
296
+ # add_color('#c0e9d3')
297
+ def add_color(colorname)
298
+ @colors << colorname
299
+ end
300
+
301
+ # Replace the entire color list with a new array of colors. Also
302
+ # aliased as the colors= setter method.
303
+ #
304
+ # If you specify fewer colors than the number of datasets you intend
305
+ # to draw, 'increment_color' will cycle through the array, reusing
306
+ # colors as needed.
307
+ #
308
+ # Note that (as with the 'theme' method), you should set up your color
309
+ # list before you send your data (via the 'data' method). Calls to the
310
+ # 'data' method made prior to this call will use whatever color scheme
311
+ # was in place at the time data was called.
312
+ #
313
+ # Example:
314
+ # replace_colors ['#cc99cc', '#d9e043', '#34d8a2']
315
+ def replace_colors(color_list=[])
316
+ @colors = color_list
317
+ @color_index = 0
318
+ end
319
+
320
+ # You can set a theme manually. Assign a hash to this method before you
321
+ # send your data.
322
+ #
323
+ # graph.theme = {
324
+ # :colors => %w(orange purple green white red),
325
+ # :marker_color => 'blue',
326
+ # :background_colors => ['black', 'grey', :top_bottom]
327
+ # }
328
+ #
329
+ # :background_image => 'squirrel.png' is also possible.
330
+ #
331
+ # (Or hopefully something better looking than that.)
332
+ #
333
+ def theme=(options)
334
+ reset_themes
335
+
336
+ defaults = {
337
+ :colors => %w(black white),
338
+ :additional_line_colors => [],
339
+ :marker_color => 'white',
340
+ :marker_shadow_color => nil,
341
+ :font_color => 'black',
342
+ :background_colors => nil,
343
+ :background_image => nil
344
+ }
345
+ @theme_options = defaults.merge options
346
+
347
+ @colors = @theme_options[:colors]
348
+ @marker_color = @theme_options[:marker_color]
349
+ @marker_shadow_color = @theme_options[:marker_shadow_color]
350
+ @font_color = @theme_options[:font_color] || @marker_color
351
+ @additional_line_colors = @theme_options[:additional_line_colors]
352
+
353
+ render_background
354
+ end
355
+
356
+ def theme_keynote
357
+ self.theme = Themes::KEYNOTE
358
+ end
359
+
360
+ def theme_37signals
361
+ self.theme = Themes::THIRTYSEVEN_SIGNALS
362
+ end
363
+
364
+ def theme_rails_keynote
365
+ self.theme = Themes::RAILS_KEYNOTE
366
+ end
367
+
368
+ def theme_odeo
369
+ self.theme = Themes::ODEO
370
+ end
371
+
372
+ def theme_pastel
373
+ self.theme = Themes::PASTEL
374
+ end
375
+
376
+ def theme_greyscale
377
+ self.theme = Themes::GREYSCALE
378
+ end
379
+
380
+ # Parameters are an array where the first element is the name of the dataset
381
+ # and the value is an array of values to plot.
382
+ #
383
+ # Can be called multiple times with different datasets for a multi-valued
384
+ # graph.
385
+ #
386
+ # If the color argument is nil, the next color from the default theme will
387
+ # be used.
388
+ #
389
+ # NOTE: If you want to use a preset theme, you must set it before calling
390
+ # data().
391
+ #
392
+ # Example:
393
+ # data("Bart S.", [95, 45, 78, 89, 88, 76], '#ffcc00')
394
+ def data(name, data_points=[], color=nil)
395
+ data_points = Array(data_points) # make sure it's an array
396
+ @data << [name, data_points, color]
397
+ # Set column count if this is larger than previous counts
398
+ @column_count = (data_points.length > @column_count) ? data_points.length : @column_count
399
+
400
+ # Pre-normalize
401
+ data_points.each do |data_point|
402
+ next if data_point.nil?
403
+
404
+ # Setup max/min so spread starts at the low end of the data points
405
+ if @maximum_value.nil? && @minimum_value.nil?
406
+ @maximum_value = @minimum_value = data_point
407
+ end
408
+
409
+ # TODO Doesn't work with stacked bar graphs
410
+ # Original: @maximum_value = larger_than_max?(data_point, index) ? max(data_point, index) : @maximum_value
411
+ @maximum_value = larger_than_max?(data_point) ? data_point : @maximum_value
412
+ @has_data = true if @maximum_value >= 0
413
+
414
+ @minimum_value = less_than_min?(data_point) ? data_point : @minimum_value
415
+ @has_data = true if @minimum_value < 0
416
+ end
417
+ end
418
+
419
+ # Writes the graph to a file. Defaults to 'graph.png'
420
+ #
421
+ # Example:
422
+ # write('graphs/my_pretty_graph.png')
423
+ def write(filename='graph.png')
424
+ draw
425
+ @base_image.write(filename)
426
+ end
427
+
428
+ # Return the graph as a rendered binary blob.
429
+ def to_blob(fileformat='PNG')
430
+ draw
431
+ @base_image.to_blob do
432
+ self.format = fileformat
433
+ end
434
+ end
435
+
436
+
437
+ protected
438
+
439
+ # Overridden by subclasses to do the actual plotting of the graph.
440
+ #
441
+ # Subclasses should start by calling super() for this method.
442
+ def draw
443
+ # Maybe should be done in one of the following functions for more granularity.
444
+ unless @has_data
445
+ draw_no_data
446
+ return
447
+ end
448
+
449
+ setup_data
450
+ setup_drawing
451
+
452
+ debug {
453
+ # Outer margin
454
+ @d.rectangle(@left_margin, @top_margin,
455
+ @raw_columns - @right_margin, @raw_rows - @bottom_margin)
456
+ # Graph area box
457
+ @d.rectangle(@graph_left, @graph_top, @graph_right, @graph_bottom)
458
+ }
459
+
460
+ draw_legend
461
+ draw_line_markers
462
+ draw_axis_labels
463
+ draw_title
464
+ end
465
+
466
+ # Perform data manipulation before calculating chart measurements
467
+ def setup_data # :nodoc:
468
+ if @y_axis_increment && !@hide_line_markers
469
+ @maximum_value = [@y_axis_increment, @maximum_value, (@maximum_value.to_f / @y_axis_increment).round * @y_axis_increment].max
470
+ @minimum_value = [@minimum_value, (@minimum_value.to_f / @y_axis_increment).round * @y_axis_increment].min
471
+ end
472
+ make_stacked if @stacked
473
+ end
474
+
475
+ # Calculates size of drawable area and generates normalized data.
476
+ #
477
+ # * line markers
478
+ # * legend
479
+ # * title
480
+ def setup_drawing
481
+ calculate_spread
482
+ sort_data if @sort # Sort data with avg largest values set first (for display)
483
+ set_colors
484
+ normalize
485
+ setup_graph_measurements
486
+ sort_norm_data if @sorted_drawing # Sort norm_data with avg largest values set first (for display)
487
+ end
488
+
489
+ # Make copy of data with values scaled between 0-100
490
+ def normalize(force=false)
491
+ if @norm_data.nil? || force
492
+ @norm_data = []
493
+ return unless @has_data
494
+
495
+ @data.each do |data_row|
496
+ norm_data_points = []
497
+ data_row[DATA_VALUES_INDEX].each do |data_point|
498
+ if data_point.nil?
499
+ norm_data_points << nil
500
+ else
501
+ norm_data_points << ((data_point.to_f - @minimum_value.to_f) / @spread)
502
+ end
503
+ end
504
+ if @show_labels_for_bar_values
505
+ @norm_data << [data_row[DATA_LABEL_INDEX], norm_data_points, data_row[DATA_COLOR_INDEX], data_row[DATA_VALUES_INDEX]]
506
+ else
507
+ @norm_data << [data_row[DATA_LABEL_INDEX], norm_data_points, data_row[DATA_COLOR_INDEX]]
508
+ end
509
+ end
510
+ end
511
+ end
512
+
513
+ def calculate_spread # :nodoc:
514
+ @spread = @maximum_value.to_f - @minimum_value.to_f
515
+ @spread = @spread > 0 ? @spread : 1
516
+ end
517
+
518
+ ##
519
+ # Calculates size of drawable area, general font dimensions, etc.
520
+
521
+ def setup_graph_measurements
522
+ @marker_caps_height = @hide_line_markers ? 0 :
523
+ calculate_caps_height(@marker_font_size)
524
+ @title_caps_height = @hide_title ? 0 :
525
+ calculate_caps_height(@title_font_size)
526
+ @legend_caps_height = @hide_legend ? 0 :
527
+ calculate_caps_height(@legend_font_size)
528
+
529
+ if @hide_line_markers
530
+ (@graph_left,
531
+ @graph_right_margin,
532
+ @graph_bottom_margin) = [@left_margin, @right_margin, @bottom_margin]
533
+ else
534
+ if @has_left_labels
535
+ longest_left_label_width = calculate_width(@marker_font_size,
536
+ labels.values.inject('') { |value, memo| (value.to_s.length > memo.to_s.length) ? value : memo }) * 1.25
537
+ else
538
+ longest_left_label_width = calculate_width(@marker_font_size,
539
+ label(@maximum_value.to_f, @increment))
540
+ end
541
+
542
+ # Shift graph if left line numbers are hidden
543
+ line_number_width = @hide_line_numbers && !@has_left_labels ?
544
+ 0.0 :
545
+ (longest_left_label_width + LABEL_MARGIN * 2)
546
+
547
+ @graph_left = @left_margin +
548
+ line_number_width +
549
+ (@y_axis_label.nil? ? 0.0 : @marker_caps_height + LABEL_MARGIN * 2)
550
+
551
+ # Make space for half the width of the rightmost column label.
552
+ # Might be greater than the number of columns if between-style bar markers are used.
553
+ last_label = @labels.keys.sort.last.to_i
554
+ extra_room_for_long_label = (last_label >= (@column_count-1) && @center_labels_over_point) ?
555
+ calculate_width(@marker_font_size, @labels[last_label]) / 2.0 :
556
+ 0
557
+ @graph_right_margin = @right_margin + extra_room_for_long_label
558
+
559
+ @graph_bottom_margin = @bottom_margin +
560
+ @marker_caps_height + LABEL_MARGIN
561
+ end
562
+
563
+ @graph_right = @raw_columns - @graph_right_margin
564
+ @graph_width = @raw_columns - @graph_left - @graph_right_margin
565
+
566
+ # When @hide title, leave a title_margin space for aesthetics.
567
+ # Same with @hide_legend
568
+ @graph_top = @legend_at_bottom ? @top_margin : (@top_margin +
569
+ (@hide_title ? title_margin : @title_caps_height + title_margin) +
570
+ (@hide_legend ? legend_margin : @legend_caps_height + legend_margin))
571
+
572
+ x_axis_label_height = @x_axis_label.nil? ? 0.0 :
573
+ @marker_caps_height + LABEL_MARGIN
574
+ # FIXME: Consider chart types other than bar
575
+ @graph_bottom = @raw_rows - @graph_bottom_margin - x_axis_label_height - @label_stagger_height
576
+ @graph_height = @graph_bottom - @graph_top
577
+ end
578
+
579
+ # Draw the optional labels for the x axis and y axis.
580
+ def draw_axis_labels
581
+ unless @x_axis_label.nil?
582
+ # X Axis
583
+ # Centered vertically and horizontally by setting the
584
+ # height to 1.0 and the width to the width of the graph.
585
+ x_axis_label_y_coordinate = @graph_bottom + LABEL_MARGIN * 2 + @marker_caps_height
586
+
587
+ # TODO Center between graph area
588
+ @d.fill = @font_color
589
+ @d.font = @font if @font
590
+ @d.stroke('transparent')
591
+ @d.pointsize = scale_fontsize(@marker_font_size)
592
+ @d.gravity = NorthGravity
593
+ @d = @d.annotate_scaled(@base_image,
594
+ @raw_columns, 1.0,
595
+ 0.0, x_axis_label_y_coordinate,
596
+ @x_axis_label, @scale)
597
+ debug { @d.line 0.0, x_axis_label_y_coordinate, @raw_columns, x_axis_label_y_coordinate }
598
+ end
599
+
600
+ unless @y_axis_label.nil?
601
+ # Y Axis, rotated vertically
602
+ @d.rotation = -90.0
603
+ @d.gravity = CenterGravity
604
+ @d = @d.annotate_scaled(@base_image,
605
+ 1.0, @raw_rows,
606
+ @left_margin + @marker_caps_height / 2.0, 0.0,
607
+ @y_axis_label, @scale)
608
+ @d.rotation = 90.0
609
+ end
610
+ end
611
+
612
+ # Draws horizontal background lines and labels
613
+ def draw_line_markers
614
+ return if @hide_line_markers
615
+
616
+ @d = @d.stroke_antialias false
617
+
618
+ if @y_axis_increment.nil?
619
+ # Try to use a number of horizontal lines that will come out even.
620
+ #
621
+ # TODO Do the same for larger numbers...100, 75, 50, 25
622
+ if @marker_count.nil?
623
+ (3..7).each do |lines|
624
+ if @spread % lines == 0.0
625
+ @marker_count = lines
626
+ break
627
+ end
628
+ end
629
+ @marker_count ||= 4
630
+ end
631
+ @increment = (@spread > 0 && @marker_count > 0) ? significant(@spread / @marker_count) : 1
632
+ else
633
+ # TODO Make this work for negative values
634
+ @marker_count = (@spread / @y_axis_increment).to_i
635
+ @increment = @y_axis_increment
636
+ end
637
+ @increment_scaled = @graph_height.to_f / (@spread / @increment)
638
+
639
+ # Draw horizontal line markers and annotate with numbers
640
+ (0..@marker_count).each do |index|
641
+ y = @graph_top + @graph_height - index.to_f * @increment_scaled
642
+
643
+ @d = @d.fill(@marker_color)
644
+
645
+ # FIXME(uwe): Workaround for Issue #66
646
+ # https://github.com/topfunky/gruff/issues/66
647
+ # https://github.com/rmagick/rmagick/issues/82
648
+ # Remove if the issue gets fixed.
649
+ y += 0.001 unless defined?(JRUBY_VERSION)
650
+ # EMXIF
651
+
652
+ @d = @d.line(@graph_left, y, @graph_right, y)
653
+ #If the user specified a marker shadow color, draw a shadow just below it
654
+ unless @marker_shadow_color.nil?
655
+ @d = @d.fill(@marker_shadow_color)
656
+ @d = @d.line(@graph_left, y + 1, @graph_right, y + 1)
657
+ end
658
+
659
+ marker_label = BigDecimal(index.to_s) * BigDecimal(@increment.to_s) +
660
+ BigDecimal(@minimum_value.to_s)
661
+
662
+ unless @hide_line_numbers
663
+ @d.fill = @font_color
664
+ @d.font = @font if @font
665
+ @d.stroke('transparent')
666
+ @d.pointsize = scale_fontsize(@marker_font_size)
667
+ @d.gravity = EastGravity
668
+
669
+ # Vertically center with 1.0 for the height
670
+ @d = @d.annotate_scaled(@base_image,
671
+ @graph_left - LABEL_MARGIN, 1.0,
672
+ 0.0, y,
673
+ label(marker_label, @increment), @scale)
674
+ end
675
+ end
676
+
677
+ # # Submitted by a contibutor...the utility escapes me
678
+ # i = 0
679
+ # @additional_line_values.each do |value|
680
+ # @increment_scaled = @graph_height.to_f / (@maximum_value.to_f / value)
681
+ #
682
+ # y = @graph_top + @graph_height - @increment_scaled
683
+ #
684
+ # @d = @d.stroke(@additional_line_colors[i])
685
+ # @d = @d.line(@graph_left, y, @graph_right, y)
686
+ #
687
+ #
688
+ # @d.fill = @additional_line_colors[i]
689
+ # @d.font = @font if @font
690
+ # @d.stroke('transparent')
691
+ # @d.pointsize = scale_fontsize(@marker_font_size)
692
+ # @d.gravity = EastGravity
693
+ # @d = @d.annotate_scaled( @base_image,
694
+ # 100, 20,
695
+ # -10, y - (@marker_font_size/2.0),
696
+ # "", @scale)
697
+ # i += 1
698
+ # end
699
+
700
+ @d = @d.stroke_antialias true
701
+ end
702
+
703
+ ##
704
+ # Return the sum of values in an array.
705
+ #
706
+ # Duplicated to not conflict with active_support in Rails.
707
+
708
+ def sum(arr)
709
+ arr.inject(0) { |i, m| m + i }
710
+ end
711
+
712
+ ##
713
+ # Return a calculation of center
714
+
715
+ def center(size)
716
+ (@raw_columns - size) / 2
717
+ end
718
+
719
+ ##
720
+ # Draws a legend with the names of the datasets matched
721
+ # to the colors used to draw them.
722
+
723
+ def draw_legend
724
+ return if @hide_legend
725
+
726
+ @legend_labels = @data.collect { |item| item[DATA_LABEL_INDEX] }
727
+
728
+ legend_square_width = @legend_box_size # small square with color of this item
729
+
730
+ # May fix legend drawing problem at small sizes
731
+ @d.font = @font if @font
732
+ @d.pointsize = @legend_font_size
733
+
734
+ label_widths = [[]] # Used to calculate line wrap
735
+ @legend_labels.each do |label|
736
+ metrics = @d.get_type_metrics(@base_image, label.to_s)
737
+ label_width = metrics.width + legend_square_width * 2.7
738
+ label_widths.last.push label_width
739
+
740
+ if sum(label_widths.last) > (@raw_columns * 0.9)
741
+ label_widths.push [label_widths.last.pop]
742
+ end
743
+ end
744
+
745
+ current_x_offset = center(sum(label_widths.first))
746
+ current_y_offset = @legend_at_bottom ? @graph_height + title_margin : (@hide_title ?
747
+ @top_margin + title_margin :
748
+ @top_margin + title_margin + @title_caps_height)
749
+
750
+ @legend_labels.each_with_index do |legend_label, index|
751
+
752
+ # Draw label
753
+ @d.fill = @font_color
754
+ @d.font = @font if @font
755
+ @d.pointsize = scale_fontsize(@legend_font_size)
756
+ @d.stroke('transparent')
757
+ @d.font_weight = NormalWeight
758
+ @d.gravity = WestGravity
759
+ @d = @d.annotate_scaled(@base_image,
760
+ @raw_columns, 1.0,
761
+ current_x_offset + (legend_square_width * 1.7), current_y_offset,
762
+ legend_label.to_s, @scale)
763
+
764
+ # Now draw box with color of this dataset
765
+ @d = @d.stroke('transparent')
766
+ @d = @d.fill @data[index][DATA_COLOR_INDEX]
767
+ @d = @d.rectangle(current_x_offset,
768
+ current_y_offset - legend_square_width / 2.0,
769
+ current_x_offset + legend_square_width,
770
+ current_y_offset + legend_square_width / 2.0)
771
+
772
+ @d.pointsize = @legend_font_size
773
+ metrics = @d.get_type_metrics(@base_image, legend_label.to_s)
774
+ current_string_offset = metrics.width + (legend_square_width * 2.7)
775
+
776
+ # Handle wrapping
777
+ label_widths.first.shift
778
+ if label_widths.first.empty?
779
+ debug { @d.line 0.0, current_y_offset, @raw_columns, current_y_offset }
780
+
781
+ label_widths.shift
782
+ current_x_offset = center(sum(label_widths.first)) unless label_widths.empty?
783
+ line_height = [@legend_caps_height, legend_square_width].max + legend_margin
784
+ if label_widths.length > 0
785
+ # Wrap to next line and shrink available graph dimensions
786
+ current_y_offset += line_height
787
+ @graph_top += line_height
788
+ @graph_height = @graph_bottom - @graph_top
789
+ end
790
+ else
791
+ current_x_offset += current_string_offset
792
+ end
793
+ end
794
+ @color_index = 0
795
+ end
796
+
797
+ # Draws a title on the graph.
798
+ def draw_title
799
+ return if (@hide_title || @title.nil?)
800
+
801
+ @d.fill = @font_color
802
+ @d.font = @font if @font
803
+ @d.stroke('transparent')
804
+ @d.pointsize = scale_fontsize(@title_font_size)
805
+ @d.font_weight = BoldWeight
806
+ @d.gravity = NorthGravity
807
+ @d = @d.annotate_scaled(@base_image,
808
+ @raw_columns, 1.0,
809
+ 0, @top_margin,
810
+ @title, @scale)
811
+ end
812
+
813
+ # Draws column labels below graph, centered over x_offset
814
+ #--
815
+ # TODO Allow WestGravity as an option
816
+ def draw_label(x_offset, index)
817
+ return if @hide_line_markers
818
+
819
+ if !@labels[index].nil? && @labels_seen[index].nil?
820
+ y_offset = @graph_bottom + LABEL_MARGIN
821
+
822
+ # TESTME
823
+ # FIXME: Consider chart types other than bar
824
+ # TODO: See if index.odd? is the best stragegy
825
+ y_offset += @label_stagger_height if index.odd?
826
+
827
+ label_text = @labels[index]
828
+
829
+ # TESTME
830
+ # FIXME: Consider chart types other than bar
831
+ if label_text.size > @label_max_size
832
+ if @label_truncation_style == :trailing_dots
833
+ if @label_max_size > 3
834
+ # 4 because '...' takes up 3 chars
835
+ label_text = "#{label_text[0 .. (@label_max_size - 4)]}..."
836
+ end
837
+ else # @label_truncation_style is :absolute (default)
838
+ label_text = label_text[0 .. (@label_max_size - 1)]
839
+ end
840
+
841
+ end
842
+
843
+ if x_offset >= @graph_left && x_offset <= @graph_right
844
+ @d.fill = @font_color
845
+ @d.font = @font if @font
846
+ @d.stroke('transparent')
847
+ @d.font_weight = NormalWeight
848
+ @d.pointsize = scale_fontsize(@marker_font_size)
849
+ @d.gravity = NorthGravity
850
+ @d = @d.annotate_scaled(@base_image,
851
+ 1.0, 1.0,
852
+ x_offset, y_offset,
853
+ label_text, @scale)
854
+ end
855
+ @labels_seen[index] = 1
856
+ debug { @d.line 0.0, y_offset, @raw_columns, y_offset }
857
+ end
858
+ end
859
+
860
+ # Draws the data value over the data point in bar graphs
861
+ def draw_value_label(x_offset, y_offset, data_point, bar_value=false)
862
+ return if @hide_line_markers && !bar_value
863
+
864
+ #y_offset = @graph_bottom + LABEL_MARGIN
865
+
866
+ @d.fill = @font_color
867
+ @d.font = @font if @font
868
+ @d.stroke('transparent')
869
+ @d.font_weight = NormalWeight
870
+ @d.pointsize = scale_fontsize(@marker_font_size)
871
+ @d.gravity = NorthGravity
872
+ @d = @d.annotate_scaled(@base_image,
873
+ 1.0, 1.0,
874
+ x_offset, y_offset,
875
+ data_point.to_s, @scale)
876
+
877
+ debug { @d.line 0.0, y_offset, @raw_columns, y_offset }
878
+ end
879
+
880
+ # Shows an error message because you have no data.
881
+ def draw_no_data
882
+ @d.fill = @font_color
883
+ @d.font = @font if @font
884
+ @d.stroke('transparent')
885
+ @d.font_weight = NormalWeight
886
+ @d.pointsize = scale_fontsize(80)
887
+ @d.gravity = CenterGravity
888
+ @d = @d.annotate_scaled(@base_image,
889
+ @raw_columns, @raw_rows/2.0,
890
+ 0, 10,
891
+ @no_data_message, @scale)
892
+ end
893
+
894
+ # Finds the best background to render based on the provided theme options.
895
+ #
896
+ # Creates a @base_image to draw on.
897
+ def render_background
898
+ case @theme_options[:background_colors]
899
+ when Array
900
+ @base_image = render_gradiated_background(@theme_options[:background_colors][0], @theme_options[:background_colors][1], @theme_options[:background_direction])
901
+ when String
902
+ @base_image = render_solid_background(@theme_options[:background_colors])
903
+ else
904
+ @base_image = render_image_background(*@theme_options[:background_image])
905
+ end
906
+ end
907
+
908
+ # Make a new image at the current size with a solid +color+.
909
+ def render_solid_background(color)
910
+ Image.new(@columns, @rows) {
911
+ self.background_color = color
912
+ }
913
+ end
914
+
915
+ # Use with a theme definition method to draw a gradiated background.
916
+ def render_gradiated_background(top_color, bottom_color, direct = :top_bottom)
917
+ case direct
918
+ when :bottom_top
919
+ gradient_fill = GradientFill.new(0, 0, 100, 0, bottom_color, top_color)
920
+ when :left_right
921
+ gradient_fill = GradientFill.new(0, 0, 0, 100, top_color, bottom_color)
922
+ when :right_left
923
+ gradient_fill = GradientFill.new(0, 0, 0, 100, bottom_color, top_color)
924
+ when :topleft_bottomright
925
+ gradient_fill = GradientFill.new(0, 100, 100, 0, top_color, bottom_color)
926
+ when :topright_bottomleft
927
+ gradient_fill = GradientFill.new(0, 0, 100, 100, bottom_color, top_color)
928
+ else
929
+ gradient_fill = GradientFill.new(0, 0, 100, 0, top_color, bottom_color)
930
+ end
931
+ Image.new(@columns, @rows, gradient_fill)
932
+ end
933
+
934
+ # Use with a theme to use an image (800x600 original) background.
935
+ def render_image_background(image_path)
936
+ image = Image.read(image_path)
937
+ if @scale != 1.0
938
+ image[0].resize!(@scale) # TODO Resize with new scale (crop if necessary for wide graph)
939
+ end
940
+ image[0]
941
+ end
942
+
943
+ # Use with a theme to make a transparent background
944
+ def render_transparent_background
945
+ Image.new(@columns, @rows) do
946
+ self.background_color = 'transparent'
947
+ end
948
+ end
949
+
950
+ # Resets everything to defaults (except data).
951
+ def reset_themes
952
+ @color_index = 0
953
+ @labels_seen = {}
954
+ @theme_options = {}
955
+
956
+ @d = Draw.new
957
+ # Scale down from 800x600 used to calculate drawing.
958
+ @d = @d.scale(@scale, @scale)
959
+ end
960
+
961
+ def scale(value) # :nodoc:
962
+ value * @scale
963
+ end
964
+
965
+ # Return a comparable fontsize for the current graph.
966
+ def scale_fontsize(value)
967
+ value * @scale
968
+ end
969
+
970
+ def clip_value_if_greater_than(value, max_value) # :nodoc:
971
+ (value > max_value) ? max_value : value
972
+ end
973
+
974
+ # Overridden by subclasses such as stacked bar.
975
+ def larger_than_max?(data_point) # :nodoc:
976
+ data_point > @maximum_value
977
+ end
978
+
979
+ def less_than_min?(data_point) # :nodoc:
980
+ data_point < @minimum_value
981
+ end
982
+
983
+ def significant(i) # :nodoc:
984
+ return 1.0 if i == 0 # Keep from going into infinite loop
985
+ inc = BigDecimal(i.to_s)
986
+ factor = BigDecimal('1.0')
987
+ while inc < 10
988
+ inc *= 10
989
+ factor /= 10
990
+ end
991
+
992
+ while inc > 100
993
+ inc /= 10
994
+ factor *= 10
995
+ end
996
+
997
+ res = inc.floor * factor
998
+ if res.to_i.to_f == res
999
+ res.to_i
1000
+ else
1001
+ res
1002
+ end
1003
+ end
1004
+
1005
+ # Sort with largest overall summed value at front of array.
1006
+ def sort_data
1007
+ @data = @data.sort_by { |a| -a[DATA_VALUES_INDEX].inject(0) { |sum, num| sum + num.to_f } }
1008
+ end
1009
+
1010
+ # Set the color for each data set unless it was gived in the data(...) call.
1011
+ def set_colors
1012
+ @data.each { |a| a[DATA_COLOR_INDEX] ||= increment_color }
1013
+ end
1014
+
1015
+ # Sort with largest overall summed value at front of array so it shows up
1016
+ # correctly in the drawn graph.
1017
+ def sort_norm_data
1018
+ @norm_data =
1019
+ @norm_data.sort_by { |a| -a[DATA_VALUES_INDEX].inject(0) { |sum, num| sum + num.to_f } }
1020
+ end
1021
+
1022
+ # Used by StackedBar and child classes.
1023
+ #
1024
+ # May need to be moved to the StackedBar class.
1025
+ def get_maximum_by_stack
1026
+ # Get sum of each stack
1027
+ max_hash = {}
1028
+ @data.each do |data_set|
1029
+ data_set[DATA_VALUES_INDEX].each_with_index do |data_point, i|
1030
+ max_hash[i] = 0.0 unless max_hash[i]
1031
+ max_hash[i] += data_point.to_f
1032
+ end
1033
+ end
1034
+
1035
+ # @maximum_value = 0
1036
+ max_hash.keys.each do |key|
1037
+ @maximum_value = max_hash[key] if max_hash[key] > @maximum_value
1038
+ end
1039
+ @minimum_value = 0
1040
+ end
1041
+
1042
+ def make_stacked # :nodoc:
1043
+ stacked_values = Array.new(@column_count, 0)
1044
+ @data.each do |value_set|
1045
+ value_set[DATA_VALUES_INDEX].each_with_index do |value, index|
1046
+ stacked_values[index] += value
1047
+ end
1048
+ value_set[DATA_VALUES_INDEX] = stacked_values.dup
1049
+ end
1050
+ end
1051
+
1052
+ private
1053
+
1054
+ # Takes a block and draws it if DEBUG is true.
1055
+ #
1056
+ # Example:
1057
+ # debug { @d.rectangle x1, y1, x2, y2 }
1058
+ def debug
1059
+ if DEBUG
1060
+ @d = @d.fill 'transparent'
1061
+ @d = @d.stroke 'turquoise'
1062
+ @d = yield
1063
+ end
1064
+ end
1065
+
1066
+ # Returns the next color in your color list.
1067
+ def increment_color
1068
+ @color_index = (@color_index + 1) % @colors.length
1069
+ @colors[@color_index - 1]
1070
+ end
1071
+
1072
+ # Return a formatted string representing a number value that should be
1073
+ # printed as a label.
1074
+ def label(value, increment)
1075
+ label = if increment
1076
+ if increment >= 10 || (increment * 1) == (increment * 1).to_i.to_f
1077
+ sprintf('%0i', value)
1078
+ elsif increment >= 1.0 || (increment * 10) == (increment * 10).to_i.to_f
1079
+ sprintf('%0.1f', value)
1080
+ elsif increment >= 0.1 || (increment * 100) == (increment * 100).to_i.to_f
1081
+ sprintf('%0.2f', value)
1082
+ elsif increment >= 0.01 || (increment * 1000) == (increment * 1000).to_i.to_f
1083
+ sprintf('%0.3f', value)
1084
+ elsif increment >= 0.001 || (increment * 10000) == (increment * 10000).to_i.to_f
1085
+ sprintf('%0.4f', value)
1086
+ else
1087
+ value.to_s
1088
+ end
1089
+ elsif (@spread.to_f % (@marker_count.to_f==0 ? 1 : @marker_count.to_f) == 0) || !@y_axis_increment.nil?
1090
+ value.to_i.to_s
1091
+ elsif @spread > 10.0
1092
+ sprintf('%0i', value)
1093
+ elsif @spread >= 3.0
1094
+ sprintf('%0.2f', value)
1095
+ else
1096
+ value.to_s
1097
+ end
1098
+
1099
+ parts = label.split('.')
1100
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{THOUSAND_SEPARATOR}")
1101
+ parts.join('.')
1102
+ end
1103
+
1104
+ # Returns the height of the capital letter 'X' for the current font and
1105
+ # size.
1106
+ #
1107
+ # Not scaled since it deals with dimensions that the regular scaling will
1108
+ # handle.
1109
+ def calculate_caps_height(font_size)
1110
+ @d.pointsize = font_size
1111
+ @d.font = @font if @font
1112
+ @d.get_type_metrics(@base_image, 'X').height
1113
+ end
1114
+
1115
+ # Returns the width of a string at this pointsize.
1116
+ #
1117
+ # Not scaled since it deals with dimensions that the regular
1118
+ # scaling will handle.
1119
+ def calculate_width(font_size, text)
1120
+ return 0 if text.nil?
1121
+ @d.pointsize = font_size
1122
+ @d.font = @font if @font
1123
+ @d.get_type_metrics(@base_image, text.to_s).width
1124
+ end
1125
+
1126
+ end # Gruff::Base
1127
+
1128
+ class IncorrectNumberOfDatasetsException < StandardError;
1129
+ end
1130
+
1131
+ end # Gruff
1132
+
1133
+ module Magick
1134
+
1135
+ class Draw
1136
+
1137
+ # Additional method to scale annotation text since Draw.scale doesn't.
1138
+ def annotate_scaled(img, width, height, x, y, text, scale)
1139
+ scaled_width = (width * scale) >= 1 ? (width * scale) : 1
1140
+ scaled_height = (height * scale) >= 1 ? (height * scale) : 1
1141
+
1142
+ self.annotate(img,
1143
+ scaled_width, scaled_height,
1144
+ x * scale, y * scale,
1145
+ text.gsub('%', '%%'))
1146
+ end
1147
+
1148
+ if defined? JRUBY_VERSION
1149
+ # FIXME(uwe): We should NOT need to implement this method.
1150
+ # Remove this method as soon as RMagick4J Issue #16 is fixed.
1151
+ # https://github.com/Serabe/RMagick4J/issues/16
1152
+ def fill=(fill)
1153
+ fill = {:white => '#FFFFFF'}[fill.to_sym] || fill
1154
+ @draw.fill = Magick4J.ColorDatabase.query_default(fill)
1155
+ self
1156
+ end
1157
+ # EMXIF
1158
+ end
1159
+
1160
+ end
1161
+
1162
+ end # Magick
1163
+
1164
+ class String
1165
+ #Taken from http://codesnippets.joyent.com/posts/show/330
1166
+ def commify(delimiter=',')
1167
+ self.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
1168
+ end
1169
+ end