gruff 0.14.0 → 0.17.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +28 -12
- data/.gitignore +1 -0
- data/.rubocop.yml +20 -24
- data/CHANGELOG.md +52 -0
- data/README.md +10 -3
- data/gruff.gemspec +9 -10
- data/lib/gruff/accumulator_bar.rb +1 -1
- data/lib/gruff/area.rb +6 -4
- data/lib/gruff/bar.rb +53 -31
- data/lib/gruff/base.rb +292 -184
- data/lib/gruff/bezier.rb +4 -2
- data/lib/gruff/box_plot.rb +180 -0
- data/lib/gruff/bullet.rb +6 -6
- data/lib/gruff/candlestick.rb +120 -0
- data/lib/gruff/dot.rb +11 -12
- data/lib/gruff/font.rb +3 -0
- data/lib/gruff/helper/bar_conversion.rb +6 -10
- data/lib/gruff/helper/bar_mixin.rb +25 -0
- data/lib/gruff/helper/bar_value_label.rb +24 -40
- data/lib/gruff/helper/stacked_mixin.rb +19 -1
- data/lib/gruff/histogram.rb +9 -5
- data/lib/gruff/line.rb +49 -48
- data/lib/gruff/mini/legend.rb +11 -11
- data/lib/gruff/net.rb +23 -18
- data/lib/gruff/patch/rmagick.rb +0 -1
- data/lib/gruff/patch/string.rb +1 -0
- data/lib/gruff/pie.rb +26 -12
- data/lib/gruff/renderer/dash_line.rb +3 -2
- data/lib/gruff/renderer/dot.rb +28 -15
- data/lib/gruff/renderer/line.rb +1 -3
- data/lib/gruff/renderer/rectangle.rb +6 -2
- data/lib/gruff/renderer/renderer.rb +4 -8
- data/lib/gruff/renderer/text.rb +7 -1
- data/lib/gruff/scatter.rb +64 -56
- data/lib/gruff/side_bar.rb +64 -30
- data/lib/gruff/side_stacked_bar.rb +43 -54
- data/lib/gruff/spider.rb +52 -18
- data/lib/gruff/stacked_area.rb +18 -8
- data/lib/gruff/stacked_bar.rb +59 -29
- data/lib/gruff/store/xy_data.rb +2 -0
- data/lib/gruff/version.rb +1 -1
- data/lib/gruff.rb +67 -58
- metadata +22 -21
- data/.rubocop_todo.yml +0 -116
- data/lib/gruff/scene.rb +0 -200
- data/lib/gruff/store/custom_data.rb +0 -36
data/lib/gruff/base.rb
CHANGED
@@ -45,22 +45,15 @@ module Gruff
|
|
45
45
|
# Blank space below the legend. Default is +20+.
|
46
46
|
attr_writer :legend_margin
|
47
47
|
|
48
|
-
#
|
49
|
-
|
50
|
-
#
|
51
|
-
# Not all columns need to be named.
|
52
|
-
#
|
53
|
-
# @example
|
54
|
-
# { 0 => 2005, 3 => 2006, 5 => 2007, 7 => 2008 }
|
55
|
-
attr_writer :labels
|
48
|
+
# Truncates labels if longer than max specified.
|
49
|
+
attr_writer :label_max_size
|
56
50
|
|
57
|
-
#
|
51
|
+
# How truncated labels visually appear if they exceed {#label_max_size=}.
|
58
52
|
#
|
59
|
-
#
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
attr_writer :has_left_labels
|
53
|
+
# - +:absolute+ - does not show trailing dots to indicate truncation. This is the default.
|
54
|
+
# - +:trailing_dots+ - shows trailing dots to indicate truncation (note that {#label_max_size=}
|
55
|
+
# must be greater than 3).
|
56
|
+
attr_writer :label_truncation_style
|
64
57
|
|
65
58
|
# Set a label for the bottom of the graph.
|
66
59
|
attr_writer :x_axis_label
|
@@ -68,31 +61,21 @@ module Gruff
|
|
68
61
|
# Set a label for the left side of the graph.
|
69
62
|
attr_writer :y_axis_label
|
70
63
|
|
64
|
+
# Allow passing lambda to format labels for x axis.
|
65
|
+
attr_writer :x_axis_label_format
|
66
|
+
|
67
|
+
# Allow passing lambda to format labels for y axis.
|
68
|
+
attr_writer :y_axis_label_format
|
69
|
+
|
71
70
|
# Set increment of the vertical marking lines.
|
72
71
|
attr_writer :x_axis_increment
|
73
72
|
|
74
73
|
# Set increment of the horizontal marking lines.
|
75
74
|
attr_writer :y_axis_increment
|
76
75
|
|
77
|
-
# Height of staggering between labels (Bar graph only).
|
78
|
-
attr_writer :label_stagger_height
|
79
|
-
|
80
|
-
# Truncates labels if longer than max specified.
|
81
|
-
attr_writer :label_max_size
|
82
|
-
|
83
|
-
# How truncated labels visually appear if they exceed {#label_max_size=}.
|
84
|
-
#
|
85
|
-
# - +:absolute+ - does not show trailing dots to indicate truncation. This is the default.
|
86
|
-
# - +:trailing_dots+ - shows trailing dots to indicate truncation (note that {#label_max_size=}
|
87
|
-
# must be greater than 3).
|
88
|
-
attr_writer :label_truncation_style
|
89
|
-
|
90
76
|
# Get or set the list of colors that will be used to draw the bars or lines.
|
91
77
|
attr_accessor :colors
|
92
78
|
|
93
|
-
# Set the large title of the graph displayed at the top.
|
94
|
-
attr_writer :title
|
95
|
-
|
96
79
|
# Prevent drawing of line markers. Default is +false+.
|
97
80
|
attr_writer :hide_line_markers
|
98
81
|
|
@@ -109,9 +92,6 @@ module Gruff
|
|
109
92
|
# to +"No Data."+.
|
110
93
|
attr_writer :no_data_message
|
111
94
|
|
112
|
-
# Display the legend under the graph. Default is +false+.
|
113
|
-
attr_writer :legend_at_bottom
|
114
|
-
|
115
95
|
# Set the color of the auxiliary lines.
|
116
96
|
attr_writer :marker_color
|
117
97
|
|
@@ -129,18 +109,15 @@ module Gruff
|
|
129
109
|
# first. This does not affect the legend. Default is +false+.
|
130
110
|
attr_writer :sorted_drawing
|
131
111
|
|
112
|
+
# Display the legend under the graph. Default is +false+.
|
113
|
+
attr_writer :legend_at_bottom
|
114
|
+
|
132
115
|
# Optionally set the size of the colored box by each item in the legend.
|
133
116
|
# Default is +20.0+.
|
134
117
|
#
|
135
118
|
# Will be scaled down if graph is smaller than 800px wide.
|
136
119
|
attr_writer :legend_box_size
|
137
120
|
|
138
|
-
# Allow passing lambdas to format labels for x axis.
|
139
|
-
attr_writer :x_axis_label_format
|
140
|
-
|
141
|
-
# Allow passing lambdas to format labels for y axis.
|
142
|
-
attr_writer :y_axis_label_format
|
143
|
-
|
144
121
|
# If one numerical argument is given, the graph is drawn at 4/3 ratio
|
145
122
|
# according to the given width (+800+ results in 800x600, +400+ gives 400x300,
|
146
123
|
# etc.).
|
@@ -159,6 +136,9 @@ module Gruff
|
|
159
136
|
@columns.freeze
|
160
137
|
@rows.freeze
|
161
138
|
|
139
|
+
@has_left_labels = false
|
140
|
+
@center_labels_over_point = true
|
141
|
+
|
162
142
|
initialize_graph_scale
|
163
143
|
initialize_attributes
|
164
144
|
initialize_store
|
@@ -209,11 +189,9 @@ module Gruff
|
|
209
189
|
@no_data_message = 'No Data'
|
210
190
|
|
211
191
|
@hide_line_markers = @hide_legend = @hide_title = @hide_line_numbers = @legend_at_bottom = false
|
212
|
-
@center_labels_over_point = true
|
213
|
-
@has_left_labels = false
|
214
|
-
@label_stagger_height = 0
|
215
192
|
@label_max_size = 0
|
216
193
|
@label_truncation_style = :absolute
|
194
|
+
@label_rotation = 0
|
217
195
|
|
218
196
|
@x_axis_increment = nil
|
219
197
|
@x_axis_label = @y_axis_label = nil
|
@@ -224,6 +202,67 @@ module Gruff
|
|
224
202
|
end
|
225
203
|
protected :initialize_attributes
|
226
204
|
|
205
|
+
# A hash of names for the individual columns, where the key is the array
|
206
|
+
# index for the column this label represents.
|
207
|
+
# Not all columns need to be named with hash.
|
208
|
+
#
|
209
|
+
# Or, an array corresponding to the data values.
|
210
|
+
#
|
211
|
+
# @param labels [Hash, Array] the labels.
|
212
|
+
#
|
213
|
+
# @example
|
214
|
+
# g = Gruff::Bar.new
|
215
|
+
# g.labels = { 0 => 2005, 3 => 2006, 5 => 2007, 7 => 2008 }
|
216
|
+
#
|
217
|
+
# g = Gruff::Bar.new
|
218
|
+
# g.labels = ['2005', nil, nil, '2006', nil, nil, '2007', nil, nil, '2008'] # same labels for columns
|
219
|
+
def labels=(labels)
|
220
|
+
if labels.is_a?(Array)
|
221
|
+
labels = labels.each_with_index.each_with_object({}) do |(label, index), hash|
|
222
|
+
hash[index] = label
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
@labels = labels
|
227
|
+
end
|
228
|
+
|
229
|
+
# Set a rotation for labels. You can use Default is +0+.
|
230
|
+
# You can use a rotation between +0.0+ and +45.0+, or between +0.0+ and +-45.0+.
|
231
|
+
#
|
232
|
+
# @param rotation [Numeric] the rotation.
|
233
|
+
#
|
234
|
+
def label_rotation=(rotation)
|
235
|
+
raise ArgumentError, 'rotation must be between 0.0 and 45.0 or between 0.0 and -45.0' if rotation > 45.0 || rotation < -45.0
|
236
|
+
|
237
|
+
@label_rotation = rotation.to_f
|
238
|
+
end
|
239
|
+
|
240
|
+
# Height of staggering between labels.
|
241
|
+
# @deprecated
|
242
|
+
def label_stagger_height=(_value)
|
243
|
+
warn '#label_stagger_height= is deprecated. It is no longer effective.'
|
244
|
+
end
|
245
|
+
|
246
|
+
# Set the large title of the graph displayed at the top.
|
247
|
+
# You can draw a multi-line title by putting a line break in the string
|
248
|
+
# or by setting an array as argument.
|
249
|
+
#
|
250
|
+
# @param title [String, Array] the title.
|
251
|
+
#
|
252
|
+
# @example
|
253
|
+
# g = Gruff::Bar.new
|
254
|
+
# g.title = "The graph title"
|
255
|
+
#
|
256
|
+
# g = Gruff::Bar.new
|
257
|
+
# g.title = ['The first line of title', 'The second line of title']
|
258
|
+
def title=(title)
|
259
|
+
if title.is_a?(Array)
|
260
|
+
title = title.join("\n")
|
261
|
+
end
|
262
|
+
|
263
|
+
@title = title
|
264
|
+
end
|
265
|
+
|
227
266
|
# Sets the top, bottom, left and right margins to +margin+.
|
228
267
|
#
|
229
268
|
# @param margin [Numeric] The margin size.
|
@@ -327,7 +366,8 @@ module Gruff
|
|
327
366
|
# You can set a theme manually. Assign a hash to this method before you
|
328
367
|
# send your data.
|
329
368
|
#
|
330
|
-
#
|
369
|
+
# g = Gruff::Bar.new
|
370
|
+
# g.theme = {
|
331
371
|
# colors: %w(orange purple green white red),
|
332
372
|
# marker_color: 'blue',
|
333
373
|
# background_colors: ['black', 'grey'],
|
@@ -364,7 +404,7 @@ module Gruff
|
|
364
404
|
self.marker_color = @theme_options[:marker_color]
|
365
405
|
self.font_color = @theme_options[:font_color] || @marker_color
|
366
406
|
|
367
|
-
@colors = @theme_options[:colors]
|
407
|
+
@colors = @theme_options[:colors].dup
|
368
408
|
@marker_shadow_color = @theme_options[:marker_shadow_color]
|
369
409
|
|
370
410
|
@renderer = Gruff::Renderer.new(@columns, @rows, @scale, @theme_options)
|
@@ -429,7 +469,7 @@ module Gruff
|
|
429
469
|
#
|
430
470
|
# Set it after you have given all your data to the graph object.
|
431
471
|
def minimum_value
|
432
|
-
@minimum_value || store.min
|
472
|
+
(@minimum_value || store.min).to_f
|
433
473
|
end
|
434
474
|
attr_writer :minimum_value
|
435
475
|
|
@@ -439,7 +479,7 @@ module Gruff
|
|
439
479
|
# If you use this, you must set it after you have given all your data to
|
440
480
|
# the graph object.
|
441
481
|
def maximum_value
|
442
|
-
@maximum_value || store.max
|
482
|
+
(@maximum_value || store.max).to_f
|
443
483
|
end
|
444
484
|
attr_writer :maximum_value
|
445
485
|
|
@@ -510,11 +550,13 @@ module Gruff
|
|
510
550
|
attr_reader :renderer
|
511
551
|
|
512
552
|
# Perform data manipulation before calculating chart measurements
|
513
|
-
def setup_data
|
553
|
+
def setup_data
|
514
554
|
if @y_axis_increment && !@hide_line_markers
|
515
|
-
self.maximum_value = [@y_axis_increment, maximum_value, (maximum_value
|
516
|
-
self.minimum_value = [minimum_value, (minimum_value
|
555
|
+
self.maximum_value = [@y_axis_increment, maximum_value, (maximum_value / @y_axis_increment).round * @y_axis_increment].max
|
556
|
+
self.minimum_value = [minimum_value, (minimum_value / @y_axis_increment).round * @y_axis_increment].min
|
517
557
|
end
|
558
|
+
|
559
|
+
sort_data if @sort # Sort data with avg largest values set first (for display)
|
518
560
|
end
|
519
561
|
|
520
562
|
# Calculates size of drawable area and generates normalized data.
|
@@ -525,7 +567,6 @@ module Gruff
|
|
525
567
|
def setup_drawing
|
526
568
|
calculate_spread
|
527
569
|
calculate_increment
|
528
|
-
sort_data if @sort # Sort data with avg largest values set first (for display)
|
529
570
|
set_colors
|
530
571
|
normalize
|
531
572
|
setup_graph_measurements
|
@@ -552,7 +593,7 @@ module Gruff
|
|
552
593
|
@marker_count ||= begin
|
553
594
|
count = nil
|
554
595
|
(3..7).each do |lines|
|
555
|
-
if @spread
|
596
|
+
if @spread % lines == 0.0
|
556
597
|
count = lines and break
|
557
598
|
end
|
558
599
|
end
|
@@ -565,8 +606,8 @@ module Gruff
|
|
565
606
|
store.normalize(minimum: minimum_value, spread: @spread)
|
566
607
|
end
|
567
608
|
|
568
|
-
def calculate_spread
|
569
|
-
@spread = maximum_value
|
609
|
+
def calculate_spread
|
610
|
+
@spread = maximum_value - minimum_value
|
570
611
|
@spread = @spread > 0 ? @spread : 1
|
571
612
|
end
|
572
613
|
|
@@ -579,28 +620,23 @@ module Gruff
|
|
579
620
|
end
|
580
621
|
|
581
622
|
def hide_left_label_area?
|
582
|
-
@hide_line_markers
|
623
|
+
@hide_line_markers && @y_axis_label.nil?
|
583
624
|
end
|
584
625
|
|
585
626
|
def hide_bottom_label_area?
|
586
|
-
@hide_line_markers
|
627
|
+
@hide_line_markers && @x_axis_label.nil?
|
587
628
|
end
|
588
629
|
|
589
630
|
##
|
590
631
|
# Calculates size of drawable area, general font dimensions, etc.
|
591
632
|
|
592
633
|
def setup_graph_measurements
|
593
|
-
@
|
594
|
-
@title_caps_height = setup_title_caps_height
|
595
|
-
@legend_caps_height = setup_legend_caps_height
|
596
|
-
|
597
|
-
margin_on_right = graph_right_margin
|
598
|
-
@graph_right = @raw_columns - margin_on_right
|
634
|
+
@graph_right = setup_right_margin
|
599
635
|
@graph_left = setup_left_margin
|
600
636
|
@graph_top = setup_top_margin
|
601
637
|
@graph_bottom = setup_bottom_margin
|
602
638
|
|
603
|
-
@graph_width = @
|
639
|
+
@graph_width = @graph_right - @graph_left
|
604
640
|
@graph_height = @graph_bottom - @graph_top
|
605
641
|
end
|
606
642
|
|
@@ -610,9 +646,8 @@ module Gruff
|
|
610
646
|
# X Axis
|
611
647
|
# Centered vertically and horizontally by setting the
|
612
648
|
# height to 1.0 and the width to the width of the graph.
|
613
|
-
x_axis_label_y_coordinate = @graph_bottom + LABEL_MARGIN +
|
649
|
+
x_axis_label_y_coordinate = @graph_bottom + (LABEL_MARGIN * 2) + labels_caps_height
|
614
650
|
|
615
|
-
# TODO: Center between graph area
|
616
651
|
text_renderer = Gruff::Renderer::Text.new(renderer, @x_axis_label, font: @marker_font)
|
617
652
|
text_renderer.add_to_render_queue(@raw_columns, 1.0, 0.0, x_axis_label_y_coordinate)
|
618
653
|
end
|
@@ -620,7 +655,7 @@ module Gruff
|
|
620
655
|
if @y_axis_label
|
621
656
|
# Y Axis, rotated vertically
|
622
657
|
text_renderer = Gruff::Renderer::Text.new(renderer, @y_axis_label, font: @marker_font, rotation: -90)
|
623
|
-
text_renderer.add_to_render_queue(1.0, @raw_rows, @left_margin +
|
658
|
+
text_renderer.add_to_render_queue(1.0, @raw_rows, @left_margin + (marker_caps_height / 2.0), 0.0, Magick::CenterGravity)
|
624
659
|
end
|
625
660
|
end
|
626
661
|
|
@@ -628,17 +663,15 @@ module Gruff
|
|
628
663
|
def draw_line_markers
|
629
664
|
return if @hide_line_markers
|
630
665
|
|
631
|
-
increment_scaled = @graph_height
|
666
|
+
increment_scaled = @graph_height / (@spread / @increment)
|
632
667
|
|
633
668
|
# Draw horizontal line markers and annotate with numbers
|
634
669
|
(0..marker_count).each do |index|
|
635
|
-
y = @graph_top + @graph_height - index
|
636
|
-
|
637
|
-
line_renderer = Gruff::Renderer::Line.new(renderer, color: @marker_color, shadow_color: @marker_shadow_color)
|
638
|
-
line_renderer.render(@graph_left, y, @graph_right, y)
|
670
|
+
y = @graph_top + @graph_height - (index * increment_scaled)
|
671
|
+
draw_marker_horizontal_line(y)
|
639
672
|
|
640
673
|
unless @hide_line_numbers
|
641
|
-
marker_label = BigDecimal(index.to_s) * BigDecimal(@increment.to_s) + BigDecimal(minimum_value.to_s)
|
674
|
+
marker_label = (BigDecimal(index.to_s) * BigDecimal(@increment.to_s)) + BigDecimal(minimum_value.to_s)
|
642
675
|
label = y_axis_label(marker_label, @increment)
|
643
676
|
text_renderer = Gruff::Renderer::Text.new(renderer, label, font: @marker_font)
|
644
677
|
text_renderer.add_to_render_queue(@graph_left - LABEL_MARGIN, 1.0, 0.0, y, Magick::EastGravity)
|
@@ -646,6 +679,25 @@ module Gruff
|
|
646
679
|
end
|
647
680
|
end
|
648
681
|
|
682
|
+
def draw_marker_horizontal_line(y)
|
683
|
+
Gruff::Renderer::Line.new(renderer, color: @marker_color).render(@graph_left, y, @graph_right, y)
|
684
|
+
Gruff::Renderer::Line.new(renderer, color: @marker_shadow_color).render(@graph_left, y + 1, @graph_right, y + 1) if @marker_shadow_color
|
685
|
+
end
|
686
|
+
|
687
|
+
def draw_marker_vertical_line(x, tick_mark_mode: false)
|
688
|
+
if tick_mark_mode
|
689
|
+
Gruff::Renderer::Line.new(renderer, color: @marker_color).render(x, @graph_bottom, x, @graph_bottom + 5)
|
690
|
+
if @marker_shadow_color
|
691
|
+
Gruff::Renderer::Line.new(renderer, color: @marker_shadow_color).render(x + 1, @graph_bottom, x + 1, @graph_bottom + 5)
|
692
|
+
end
|
693
|
+
else
|
694
|
+
Gruff::Renderer::Line.new(renderer, color: @marker_color).render(x, @graph_bottom, x, @graph_top)
|
695
|
+
if @marker_shadow_color
|
696
|
+
Gruff::Renderer::Line.new(renderer, color: @marker_shadow_color).render(x + 1, @graph_bottom, x + 1, @graph_top)
|
697
|
+
end
|
698
|
+
end
|
699
|
+
end
|
700
|
+
|
649
701
|
# Return a calculation of center
|
650
702
|
def center(size)
|
651
703
|
(@raw_columns - size) / 2
|
@@ -658,45 +710,46 @@ module Gruff
|
|
658
710
|
|
659
711
|
legend_labels = store.data.map(&:label)
|
660
712
|
legend_square_width = @legend_box_size # small square with color of this item
|
661
|
-
|
713
|
+
legend_label_lines = calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
|
714
|
+
line_height = [legend_caps_height, legend_square_width].max + @legend_margin
|
662
715
|
|
663
|
-
current_x_offset = center(label_widths.first.sum)
|
664
716
|
current_y_offset = begin
|
665
717
|
if @legend_at_bottom
|
666
|
-
@graph_bottom + @legend_margin + @
|
718
|
+
@graph_bottom + @legend_margin + labels_caps_height + LABEL_MARGIN + (@x_axis_label ? (LABEL_MARGIN * 2) + marker_caps_height : 0)
|
667
719
|
else
|
668
|
-
hide_title? ? @top_margin + @title_margin : @top_margin + @title_margin +
|
720
|
+
hide_title? ? @top_margin + @title_margin : @top_margin + @title_margin + title_caps_height
|
669
721
|
end
|
670
722
|
end
|
671
723
|
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
# Wrap to next line and shrink available graph dimensions
|
697
|
-
current_y_offset += line_height
|
724
|
+
index = 0
|
725
|
+
legend_label_lines.each do |(legend_labels_width, legend_labels_line)|
|
726
|
+
current_x_offset = center(legend_labels_width)
|
727
|
+
|
728
|
+
legend_labels_line.each do |legend_label|
|
729
|
+
unless legend_label.empty?
|
730
|
+
legend_label_width = calculate_width(@legend_font, legend_label)
|
731
|
+
|
732
|
+
# Draw label
|
733
|
+
text_renderer = Gruff::Renderer::Text.new(renderer, legend_label, font: @legend_font)
|
734
|
+
text_renderer.add_to_render_queue(legend_label_width,
|
735
|
+
legend_square_width,
|
736
|
+
current_x_offset + (legend_square_width * 1.7),
|
737
|
+
current_y_offset,
|
738
|
+
Magick::CenterGravity)
|
739
|
+
|
740
|
+
# Now draw box with color of this dataset
|
741
|
+
rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: store.data[index].color)
|
742
|
+
rect_renderer.render(current_x_offset,
|
743
|
+
current_y_offset,
|
744
|
+
current_x_offset + legend_square_width,
|
745
|
+
current_y_offset + legend_square_width)
|
746
|
+
|
747
|
+
current_x_offset += legend_label_width + (legend_square_width * 2.7)
|
698
748
|
end
|
749
|
+
index += 1
|
699
750
|
end
|
751
|
+
|
752
|
+
current_y_offset += line_height
|
700
753
|
end
|
701
754
|
end
|
702
755
|
|
@@ -704,7 +757,7 @@ module Gruff
|
|
704
757
|
def draw_title
|
705
758
|
return if hide_title?
|
706
759
|
|
707
|
-
metrics =
|
760
|
+
metrics = text_metrics(@title_font, @title)
|
708
761
|
if metrics.width > @raw_columns
|
709
762
|
@title_font.size = @title_font.size * (@raw_columns / metrics.width) * 0.95
|
710
763
|
end
|
@@ -714,19 +767,25 @@ module Gruff
|
|
714
767
|
end
|
715
768
|
|
716
769
|
# Draws column labels below graph, centered over x_offset
|
717
|
-
|
718
|
-
# TODO Allow WestGravity as an option
|
719
|
-
def draw_label(x_offset, index, gravity = Magick::NorthGravity)
|
770
|
+
def draw_label(x_offset, index, gravity = Magick::NorthGravity, &block)
|
720
771
|
draw_unique_label(index) do
|
721
|
-
y_offset = @graph_bottom
|
722
|
-
|
723
|
-
# TESTME
|
724
|
-
# FIXME: Consider chart types other than bar
|
725
|
-
# TODO: See if index.odd? is the best stragegy
|
726
|
-
y_offset += @label_stagger_height if index.odd?
|
772
|
+
y_offset = @graph_bottom
|
727
773
|
|
728
774
|
if x_offset >= @graph_left && x_offset <= @graph_right
|
729
|
-
|
775
|
+
width = calculate_width(@marker_font, @labels[index], rotation: @label_rotation)
|
776
|
+
height = calculate_height(@marker_font, @labels[index], rotation: @label_rotation)
|
777
|
+
case @label_rotation
|
778
|
+
when 0
|
779
|
+
x_offset
|
780
|
+
when 0..45
|
781
|
+
x_offset += (width / 2.0)
|
782
|
+
when -45..0
|
783
|
+
x_offset -= (width / 2.0)
|
784
|
+
end
|
785
|
+
y_offset += (height / 2.0) > LABEL_MARGIN ? (height / 2.0) : LABEL_MARGIN
|
786
|
+
|
787
|
+
draw_label_at(1.0, 1.0, x_offset, y_offset, @labels[index], gravity: gravity, rotation: @label_rotation)
|
788
|
+
yield if block
|
730
789
|
end
|
731
790
|
end
|
732
791
|
end
|
@@ -741,18 +800,17 @@ module Gruff
|
|
741
800
|
end
|
742
801
|
end
|
743
802
|
|
744
|
-
def draw_label_at(width, height, x, y, text, gravity
|
803
|
+
def draw_label_at(width, height, x, y, text, gravity: Magick::NorthGravity, rotation: 0)
|
745
804
|
label_text = truncate_label_text(text)
|
746
|
-
text_renderer = Gruff::Renderer::Text.new(renderer, label_text, font: @marker_font)
|
805
|
+
text_renderer = Gruff::Renderer::Text.new(renderer, label_text, font: @marker_font, rotation: rotation)
|
747
806
|
text_renderer.add_to_render_queue(width, height, x, y, gravity)
|
748
807
|
end
|
749
808
|
|
750
809
|
# Draws the data value over the data point in bar graphs
|
751
|
-
def draw_value_label(x_offset, y_offset, data_point)
|
810
|
+
def draw_value_label(width, height, x_offset, y_offset, data_point, gravity: Magick::CenterGravity)
|
752
811
|
return if @hide_line_markers
|
753
812
|
|
754
|
-
|
755
|
-
text_renderer.add_to_render_queue(1.0, 1.0, x_offset, y_offset)
|
813
|
+
draw_label_at(width, height, x_offset, y_offset, data_point, gravity: gravity)
|
756
814
|
end
|
757
815
|
|
758
816
|
# Shows an error message because you have no data.
|
@@ -773,20 +831,15 @@ module Gruff
|
|
773
831
|
@theme_options = {}
|
774
832
|
end
|
775
833
|
|
776
|
-
def scale(value)
|
834
|
+
def scale(value)
|
777
835
|
value * @scale
|
778
836
|
end
|
779
837
|
|
780
|
-
|
781
|
-
|
782
|
-
value * @scale
|
838
|
+
def clip_value_if_greater_than(value, max_value)
|
839
|
+
value > max_value ? max_value : value
|
783
840
|
end
|
784
841
|
|
785
|
-
def
|
786
|
-
(value > max_value) ? max_value : value
|
787
|
-
end
|
788
|
-
|
789
|
-
def significant(i) # :nodoc:
|
842
|
+
def significant(i)
|
790
843
|
return 1.0 if i == 0 # Keep from going into infinite loop
|
791
844
|
|
792
845
|
inc = BigDecimal(i.to_s)
|
@@ -829,27 +882,20 @@ module Gruff
|
|
829
882
|
|
830
883
|
private
|
831
884
|
|
832
|
-
def
|
885
|
+
def marker_caps_height
|
833
886
|
hide_bottom_label_area? ? 0 : calculate_caps_height(@marker_font)
|
834
887
|
end
|
835
888
|
|
836
|
-
def
|
837
|
-
|
838
|
-
end
|
839
|
-
|
840
|
-
def setup_legend_caps_height
|
841
|
-
@hide_legend ? 0 : calculate_caps_height(@legend_font)
|
889
|
+
def labels_caps_height
|
890
|
+
hide_bottom_label_area? ? 0 : calculate_labels_height(@marker_font)
|
842
891
|
end
|
843
892
|
|
844
|
-
def
|
845
|
-
|
893
|
+
def title_caps_height
|
894
|
+
hide_title? ? 0 : calculate_caps_height(@title_font) * @title.lines.to_a.size
|
846
895
|
end
|
847
896
|
|
848
|
-
def
|
849
|
-
|
850
|
-
# Might be greater than the number of columns if between-style bar markers are used.
|
851
|
-
last_label = @labels.keys.max.to_i
|
852
|
-
(last_label >= (column_count - 1) && @center_labels_over_point) ? calculate_width(@marker_font, @labels[last_label]) / 2.0 : 0
|
897
|
+
def legend_caps_height
|
898
|
+
@hide_legend ? 0 : calculate_caps_height(@legend_font)
|
853
899
|
end
|
854
900
|
|
855
901
|
def setup_left_margin
|
@@ -857,35 +903,85 @@ module Gruff
|
|
857
903
|
|
858
904
|
text = begin
|
859
905
|
if @has_left_labels
|
860
|
-
@labels.values.reduce('') { |value, memo|
|
906
|
+
@labels.values.reduce('') { |value, memo| value.to_s.length > memo.to_s.length ? value : memo }
|
861
907
|
else
|
862
|
-
y_axis_label(maximum_value
|
908
|
+
y_axis_label(maximum_value, @increment)
|
863
909
|
end
|
864
910
|
end
|
865
911
|
longest_left_label_width = calculate_width(@marker_font, truncate_label_text(text))
|
866
|
-
longest_left_label_width *= 1.25 if @has_left_labels
|
867
912
|
|
868
|
-
|
869
|
-
|
913
|
+
line_number_width = begin
|
914
|
+
if !@has_left_labels && (@hide_line_markers || @hide_line_numbers)
|
915
|
+
0.0
|
916
|
+
else
|
917
|
+
longest_left_label_width + LABEL_MARGIN
|
918
|
+
end
|
919
|
+
end
|
920
|
+
y_axis_label_width = @y_axis_label.nil? ? 0.0 : marker_caps_height + (LABEL_MARGIN * 2)
|
870
921
|
|
871
|
-
|
922
|
+
bottom_label_width = extra_left_room_for_long_label
|
923
|
+
|
924
|
+
margin = line_number_width + y_axis_label_width
|
925
|
+
@left_margin + (margin > bottom_label_width ? margin : bottom_label_width)
|
926
|
+
end
|
927
|
+
|
928
|
+
def setup_right_margin
|
929
|
+
@raw_columns - (@hide_line_markers ? @right_margin : @right_margin + extra_right_room_for_long_label)
|
930
|
+
end
|
931
|
+
|
932
|
+
def extra_left_room_for_long_label
|
933
|
+
if require_extra_side_margin?
|
934
|
+
width = calculate_width(@marker_font, truncate_label_text(@labels[0]), rotation: @label_rotation)
|
935
|
+
case @label_rotation
|
936
|
+
when 0
|
937
|
+
width / 2.0
|
938
|
+
when 0..45
|
939
|
+
0
|
940
|
+
when -45..0
|
941
|
+
width
|
942
|
+
end
|
943
|
+
else
|
944
|
+
0
|
945
|
+
end
|
946
|
+
end
|
947
|
+
|
948
|
+
def extra_right_room_for_long_label
|
949
|
+
# Make space for half the width of the rightmost column label.
|
950
|
+
# Might be greater than the number of columns if between-style bar markers are used.
|
951
|
+
last_label = @labels.keys.max.to_i
|
952
|
+
if last_label >= (column_count - 1) && require_extra_side_margin?
|
953
|
+
width = calculate_width(@marker_font, truncate_label_text(@labels[last_label]), rotation: @label_rotation)
|
954
|
+
case @label_rotation
|
955
|
+
when 0
|
956
|
+
width / 2.0
|
957
|
+
when 0..45
|
958
|
+
width
|
959
|
+
when -45..0
|
960
|
+
0
|
961
|
+
end
|
962
|
+
else
|
963
|
+
0
|
964
|
+
end
|
965
|
+
end
|
966
|
+
|
967
|
+
def require_extra_side_margin?
|
968
|
+
!hide_bottom_label_area? && @center_labels_over_point
|
872
969
|
end
|
873
970
|
|
874
971
|
def setup_top_margin
|
875
972
|
# When @hide title, leave a title_margin space for aesthetics.
|
876
973
|
# Same with @hide_legend
|
877
974
|
@top_margin +
|
878
|
-
(hide_title? ? @title_margin :
|
879
|
-
(
|
975
|
+
(hide_title? ? @title_margin : title_caps_height + @title_margin) +
|
976
|
+
(@hide_legend || @legend_at_bottom ? @legend_margin : calculate_legend_height + @legend_margin)
|
880
977
|
end
|
881
978
|
|
882
979
|
def setup_bottom_margin
|
883
|
-
graph_bottom_margin = hide_bottom_label_area? ? @bottom_margin : @bottom_margin +
|
980
|
+
graph_bottom_margin = hide_bottom_label_area? ? @bottom_margin : @bottom_margin + labels_caps_height + LABEL_MARGIN
|
884
981
|
graph_bottom_margin += (calculate_legend_height + @legend_margin) if @legend_at_bottom
|
885
982
|
|
886
|
-
x_axis_label_height = @x_axis_label.nil? ? 0.0 :
|
887
|
-
|
888
|
-
@raw_rows - graph_bottom_margin - x_axis_label_height - @label_stagger_height
|
983
|
+
x_axis_label_height = @x_axis_label.nil? ? 0.0 : marker_caps_height + (LABEL_MARGIN * 2)
|
984
|
+
@raw_rows - graph_bottom_margin - x_axis_label_height
|
889
985
|
end
|
890
986
|
|
891
987
|
def truncate_label_text(text)
|
@@ -914,12 +1010,12 @@ module Gruff
|
|
914
1010
|
sprintf('%0.2f', value)
|
915
1011
|
elsif increment >= 0.01 || (increment * 1000) == (increment * 1000).to_i.to_f
|
916
1012
|
sprintf('%0.3f', value)
|
917
|
-
elsif increment >= 0.001 || (increment *
|
1013
|
+
elsif increment >= 0.001 || (increment * 10_000) == (increment * 10_000).to_i.to_f
|
918
1014
|
sprintf('%0.4f', value)
|
919
1015
|
else
|
920
1016
|
value.to_s
|
921
1017
|
end
|
922
|
-
elsif (@spread
|
1018
|
+
elsif (@spread % (marker_count == 0 ? 1 : marker_count) == 0) || !@y_axis_increment.nil?
|
923
1019
|
value.to_i.to_s
|
924
1020
|
elsif @spread > 10.0
|
925
1021
|
sprintf('%0i', value)
|
@@ -952,44 +1048,31 @@ module Gruff
|
|
952
1048
|
end
|
953
1049
|
|
954
1050
|
def calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
|
955
|
-
|
956
|
-
|
1051
|
+
label_widths = [[]]
|
1052
|
+
label_lines = [[]]
|
957
1053
|
legend_labels.each do |label|
|
958
1054
|
width = calculate_width(@legend_font, label)
|
959
|
-
label_width = width + legend_square_width * 2.7
|
1055
|
+
label_width = width + (legend_square_width * 2.7)
|
960
1056
|
label_widths.last.push label_width
|
1057
|
+
label_lines.last.push label
|
961
1058
|
|
962
1059
|
if label_widths.last.sum > (@raw_columns * 0.9)
|
963
1060
|
label_widths.push [label_widths.last.pop]
|
1061
|
+
label_lines.push [label_lines.last.pop]
|
964
1062
|
end
|
965
1063
|
end
|
966
1064
|
|
967
|
-
label_widths
|
1065
|
+
label_widths.map(&:sum).zip(label_lines)
|
968
1066
|
end
|
969
1067
|
|
970
1068
|
def calculate_legend_height
|
971
1069
|
return 0.0 if @hide_legend
|
972
1070
|
|
973
1071
|
legend_labels = store.data.map(&:label)
|
974
|
-
|
975
|
-
|
976
|
-
legend_height = 0.0
|
977
|
-
|
978
|
-
legend_labels.each_with_index do |legend_label, _index|
|
979
|
-
next if legend_label.empty?
|
980
|
-
|
981
|
-
label_widths.first.shift
|
982
|
-
if label_widths.first.empty?
|
983
|
-
label_widths.shift
|
984
|
-
line_height = [@legend_caps_height, legend_square_width].max + @legend_margin
|
985
|
-
unless label_widths.empty?
|
986
|
-
# Wrap to next line and shrink available graph dimensions
|
987
|
-
legend_height += line_height
|
988
|
-
end
|
989
|
-
end
|
990
|
-
end
|
1072
|
+
legend_label_lines = calculate_legend_label_widths_for_each_line(legend_labels, @legend_box_size)
|
1073
|
+
line_height = [legend_caps_height, @legend_box_size].max
|
991
1074
|
|
992
|
-
|
1075
|
+
(line_height * legend_label_lines.count) + (@legend_margin * (legend_label_lines.count - 1))
|
993
1076
|
end
|
994
1077
|
|
995
1078
|
# Returns the height of the capital letter 'X' for the current font and
|
@@ -998,20 +1081,41 @@ module Gruff
|
|
998
1081
|
# Not scaled since it deals with dimensions that the regular scaling will
|
999
1082
|
# handle.
|
1000
1083
|
def calculate_caps_height(font)
|
1001
|
-
|
1002
|
-
|
1084
|
+
calculate_height(font, 'X')
|
1085
|
+
end
|
1086
|
+
|
1087
|
+
def calculate_labels_height(font)
|
1088
|
+
@labels.values.map { |label| calculate_height(font, label, rotation: @label_rotation) }.max || marker_caps_height
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
# Returns the height of a string at this point size.
|
1092
|
+
#
|
1093
|
+
# Not scaled since it deals with dimensions that the regular scaling will
|
1094
|
+
# handle.
|
1095
|
+
def calculate_height(font, text, rotation: 0)
|
1096
|
+
text = text.to_s
|
1097
|
+
return 0 if text.empty?
|
1098
|
+
|
1099
|
+
metrics = text_metrics(font, text, rotation: rotation)
|
1100
|
+
# Calculate manually because it does not return the height after rotation.
|
1101
|
+
(metrics.width * Math.sin(deg2rad(rotation))).abs + (metrics.height * Math.cos(deg2rad(rotation))).abs
|
1003
1102
|
end
|
1004
1103
|
|
1005
1104
|
# Returns the width of a string at this point size.
|
1006
1105
|
#
|
1007
1106
|
# Not scaled since it deals with dimensions that the regular
|
1008
1107
|
# scaling will handle.
|
1009
|
-
def calculate_width(font, text)
|
1108
|
+
def calculate_width(font, text, rotation: 0)
|
1010
1109
|
text = text.to_s
|
1011
1110
|
return 0 if text.empty?
|
1012
1111
|
|
1013
|
-
metrics =
|
1014
|
-
|
1112
|
+
metrics = text_metrics(font, text, rotation: rotation)
|
1113
|
+
# Calculate manually because it does not return the width after rotation.
|
1114
|
+
(metrics.width * Math.cos(deg2rad(rotation))).abs - (metrics.height * Math.sin(deg2rad(rotation))).abs
|
1115
|
+
end
|
1116
|
+
|
1117
|
+
def text_metrics(font, text, rotation: 0)
|
1118
|
+
Gruff::Renderer::Text.new(renderer, text, font: font, rotation: rotation).metrics
|
1015
1119
|
end
|
1016
1120
|
|
1017
1121
|
def calculate_increment
|
@@ -1019,7 +1123,7 @@ module Gruff
|
|
1019
1123
|
# Try to use a number of horizontal lines that will come out even.
|
1020
1124
|
#
|
1021
1125
|
# TODO Do the same for larger numbers...100, 75, 50, 25
|
1022
|
-
@increment =
|
1126
|
+
@increment = @spread > 0 && marker_count > 0 ? significant(@spread / marker_count) : 1
|
1023
1127
|
else
|
1024
1128
|
# TODO: Make this work for negative values
|
1025
1129
|
self.marker_count = (@spread / @y_axis_increment).to_i
|
@@ -1027,9 +1131,13 @@ module Gruff
|
|
1027
1131
|
end
|
1028
1132
|
end
|
1029
1133
|
|
1030
|
-
# Used for degree
|
1134
|
+
# Used for degree <=> radian conversions
|
1031
1135
|
def deg2rad(angle)
|
1032
|
-
angle *
|
1136
|
+
(angle * Math::PI) / 180.0
|
1137
|
+
end
|
1138
|
+
|
1139
|
+
def rad2deg(angle)
|
1140
|
+
(angle / Math::PI) * 180.0
|
1033
1141
|
end
|
1034
1142
|
end
|
1035
1143
|
|