gruff 0.4.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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