gruff 0.15.0-java → 0.18.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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +21 -5
- data/.gitignore +1 -0
- data/.rubocop.yml +0 -12
- data/CHANGELOG.md +52 -0
- data/README.md +14 -3
- data/gruff.gemspec +3 -4
- data/lib/gruff/accumulator_bar.rb +1 -1
- data/lib/gruff/area.rb +6 -4
- data/lib/gruff/bar.rb +53 -32
- data/lib/gruff/base.rb +297 -186
- data/lib/gruff/bezier.rb +4 -2
- data/lib/gruff/box.rb +180 -0
- data/lib/gruff/bubble.rb +99 -0
- data/lib/gruff/bullet.rb +5 -5
- 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 -43
- data/lib/gruff/helper/stacked_mixin.rb +19 -1
- data/lib/gruff/histogram.rb +9 -6
- data/lib/gruff/line.rb +67 -43
- data/lib/gruff/mini/legend.rb +15 -11
- data/lib/gruff/net.rb +23 -18
- data/lib/gruff/patch/string.rb +1 -0
- data/lib/gruff/pie.rb +26 -12
- data/lib/gruff/renderer/circle.rb +3 -1
- 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 +0 -4
- data/lib/gruff/renderer/text.rb +7 -1
- data/lib/gruff/scatter.rb +84 -81
- data/lib/gruff/side_bar.rb +64 -31
- data/lib/gruff/side_stacked_bar.rb +43 -55
- data/lib/gruff/spider.rb +52 -14
- data/lib/gruff/stacked_area.rb +18 -8
- data/lib/gruff/stacked_bar.rb +59 -29
- data/lib/gruff/store/xy_data.rb +8 -9
- data/lib/gruff/store/xy_pointsizes_data.rb +60 -0
- data/lib/gruff/version.rb +1 -1
- data/lib/gruff.rb +11 -12
- metadata +9 -6
- data/lib/gruff/scene.rb +0 -208
- 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,9 +606,9 @@ module Gruff
|
|
565
606
|
store.normalize(minimum: minimum_value, spread: @spread)
|
566
607
|
end
|
567
608
|
|
568
|
-
def calculate_spread
|
609
|
+
def calculate_spread
|
569
610
|
@spread = maximum_value.to_f - minimum_value.to_f
|
570
|
-
@spread = @spread > 0 ? @spread : 1
|
611
|
+
@spread = @spread > 0 ? @spread : 1.0
|
571
612
|
end
|
572
613
|
|
573
614
|
def hide_title?
|
@@ -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)).to_f
|
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
|
@@ -713,20 +766,15 @@ module Gruff
|
|
713
766
|
text_renderer.add_to_render_queue(@raw_columns, 1.0, 0, @top_margin)
|
714
767
|
end
|
715
768
|
|
716
|
-
# Draws column labels below graph, centered over
|
717
|
-
|
718
|
-
# TODO Allow WestGravity as an option
|
719
|
-
def draw_label(x_offset, index, gravity = Magick::NorthGravity)
|
769
|
+
# Draws column labels below graph, centered over x
|
770
|
+
def draw_label(x, index, gravity = Magick::NorthGravity, &block)
|
720
771
|
draw_unique_label(index) do
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
# FIXME: Consider chart types other than bar
|
725
|
-
# TODO: See if index.odd? is the best strategy
|
726
|
-
y_offset += @label_stagger_height if index.odd?
|
772
|
+
if x >= @graph_left && x <= @graph_right
|
773
|
+
y = @graph_bottom
|
774
|
+
x_offset, y_offset = calculate_label_offset(@marker_font, @labels[index], LABEL_MARGIN, @label_rotation)
|
727
775
|
|
728
|
-
|
729
|
-
|
776
|
+
draw_label_at(1.0, 1.0, x + x_offset, y + y_offset, @labels[index], gravity: gravity, rotation: @label_rotation)
|
777
|
+
yield if block
|
730
778
|
end
|
731
779
|
end
|
732
780
|
end
|
@@ -741,18 +789,17 @@ module Gruff
|
|
741
789
|
end
|
742
790
|
end
|
743
791
|
|
744
|
-
def draw_label_at(width, height, x, y, text, gravity
|
792
|
+
def draw_label_at(width, height, x, y, text, gravity: Magick::NorthGravity, rotation: 0)
|
745
793
|
label_text = truncate_label_text(text)
|
746
|
-
text_renderer = Gruff::Renderer::Text.new(renderer, label_text, font: @marker_font)
|
794
|
+
text_renderer = Gruff::Renderer::Text.new(renderer, label_text, font: @marker_font, rotation: rotation)
|
747
795
|
text_renderer.add_to_render_queue(width, height, x, y, gravity)
|
748
796
|
end
|
749
797
|
|
750
798
|
# Draws the data value over the data point in bar graphs
|
751
|
-
def draw_value_label(x_offset, y_offset, data_point)
|
799
|
+
def draw_value_label(width, height, x_offset, y_offset, data_point, gravity: Magick::CenterGravity)
|
752
800
|
return if @hide_line_markers
|
753
801
|
|
754
|
-
|
755
|
-
text_renderer.add_to_render_queue(1.0, 1.0, x_offset, y_offset)
|
802
|
+
draw_label_at(width, height, x_offset, y_offset, data_point, gravity: gravity)
|
756
803
|
end
|
757
804
|
|
758
805
|
# Shows an error message because you have no data.
|
@@ -773,20 +820,11 @@ module Gruff
|
|
773
820
|
@theme_options = {}
|
774
821
|
end
|
775
822
|
|
776
|
-
def
|
777
|
-
value * @scale
|
778
|
-
end
|
779
|
-
|
780
|
-
# Return a comparable fontsize for the current graph.
|
781
|
-
def scale_fontsize(value)
|
782
|
-
value * @scale
|
783
|
-
end
|
784
|
-
|
785
|
-
def clip_value_if_greater_than(value, max_value) # :nodoc:
|
823
|
+
def clip_value_if_greater_than(value, max_value)
|
786
824
|
value > max_value ? max_value : value
|
787
825
|
end
|
788
826
|
|
789
|
-
def significant(i)
|
827
|
+
def significant(i)
|
790
828
|
return 1.0 if i == 0 # Keep from going into infinite loop
|
791
829
|
|
792
830
|
inc = BigDecimal(i.to_s)
|
@@ -829,27 +867,20 @@ module Gruff
|
|
829
867
|
|
830
868
|
private
|
831
869
|
|
832
|
-
def
|
870
|
+
def marker_caps_height
|
833
871
|
hide_bottom_label_area? ? 0 : calculate_caps_height(@marker_font)
|
834
872
|
end
|
835
873
|
|
836
|
-
def
|
837
|
-
|
874
|
+
def labels_caps_height
|
875
|
+
hide_bottom_label_area? ? 0 : calculate_labels_height(@marker_font)
|
838
876
|
end
|
839
877
|
|
840
|
-
def
|
841
|
-
|
842
|
-
end
|
843
|
-
|
844
|
-
def graph_right_margin
|
845
|
-
@hide_line_markers ? @right_margin : @right_margin + extra_room_for_long_label
|
878
|
+
def title_caps_height
|
879
|
+
hide_title? ? 0 : calculate_caps_height(@title_font) * @title.lines.to_a.size
|
846
880
|
end
|
847
881
|
|
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
|
882
|
+
def legend_caps_height
|
883
|
+
@hide_legend ? 0 : calculate_caps_height(@legend_font)
|
853
884
|
end
|
854
885
|
|
855
886
|
def setup_left_margin
|
@@ -859,33 +890,83 @@ module Gruff
|
|
859
890
|
if @has_left_labels
|
860
891
|
@labels.values.reduce('') { |value, memo| value.to_s.length > memo.to_s.length ? value : memo }
|
861
892
|
else
|
862
|
-
y_axis_label(maximum_value
|
893
|
+
y_axis_label(maximum_value, @increment)
|
863
894
|
end
|
864
895
|
end
|
865
896
|
longest_left_label_width = calculate_width(@marker_font, truncate_label_text(text))
|
866
|
-
longest_left_label_width *= 1.25 if @has_left_labels
|
867
897
|
|
868
|
-
|
869
|
-
|
898
|
+
line_number_width = begin
|
899
|
+
if !@has_left_labels && (@hide_line_markers || @hide_line_numbers)
|
900
|
+
0.0
|
901
|
+
else
|
902
|
+
longest_left_label_width + LABEL_MARGIN
|
903
|
+
end
|
904
|
+
end
|
905
|
+
y_axis_label_width = @y_axis_label.nil? ? 0.0 : marker_caps_height + (LABEL_MARGIN * 2)
|
870
906
|
|
871
|
-
|
907
|
+
bottom_label_width = extra_left_room_for_long_label
|
908
|
+
|
909
|
+
margin = line_number_width + y_axis_label_width
|
910
|
+
@left_margin + (margin > bottom_label_width ? margin : bottom_label_width)
|
911
|
+
end
|
912
|
+
|
913
|
+
def setup_right_margin
|
914
|
+
@raw_columns - (@hide_line_markers ? @right_margin : @right_margin + extra_right_room_for_long_label)
|
915
|
+
end
|
916
|
+
|
917
|
+
def extra_left_room_for_long_label
|
918
|
+
if require_extra_side_margin?
|
919
|
+
width = calculate_width(@marker_font, truncate_label_text(@labels[0]), rotation: @label_rotation)
|
920
|
+
case @label_rotation
|
921
|
+
when 0
|
922
|
+
width / 2.0
|
923
|
+
when 0..45
|
924
|
+
0
|
925
|
+
when -45..0
|
926
|
+
width
|
927
|
+
end
|
928
|
+
else
|
929
|
+
0
|
930
|
+
end
|
931
|
+
end
|
932
|
+
|
933
|
+
def extra_right_room_for_long_label
|
934
|
+
# Make space for half the width of the rightmost column label.
|
935
|
+
# Might be greater than the number of columns if between-style bar markers are used.
|
936
|
+
last_label = @labels.keys.max.to_i
|
937
|
+
if last_label >= (column_count - 1) && require_extra_side_margin?
|
938
|
+
width = calculate_width(@marker_font, truncate_label_text(@labels[last_label]), rotation: @label_rotation)
|
939
|
+
case @label_rotation
|
940
|
+
when 0
|
941
|
+
width / 2.0
|
942
|
+
when 0..45
|
943
|
+
width
|
944
|
+
when -45..0
|
945
|
+
0
|
946
|
+
end
|
947
|
+
else
|
948
|
+
0
|
949
|
+
end
|
950
|
+
end
|
951
|
+
|
952
|
+
def require_extra_side_margin?
|
953
|
+
!hide_bottom_label_area? && @center_labels_over_point
|
872
954
|
end
|
873
955
|
|
874
956
|
def setup_top_margin
|
875
957
|
# When @hide title, leave a title_margin space for aesthetics.
|
876
958
|
# Same with @hide_legend
|
877
959
|
@top_margin +
|
878
|
-
(hide_title? ? @title_margin :
|
960
|
+
(hide_title? ? @title_margin : title_caps_height + @title_margin) +
|
879
961
|
(@hide_legend || @legend_at_bottom ? @legend_margin : calculate_legend_height + @legend_margin)
|
880
962
|
end
|
881
963
|
|
882
964
|
def setup_bottom_margin
|
883
|
-
graph_bottom_margin = hide_bottom_label_area? ? @bottom_margin : @bottom_margin +
|
965
|
+
graph_bottom_margin = hide_bottom_label_area? ? @bottom_margin : @bottom_margin + labels_caps_height + LABEL_MARGIN
|
884
966
|
graph_bottom_margin += (calculate_legend_height + @legend_margin) if @legend_at_bottom
|
885
967
|
|
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
|
968
|
+
x_axis_label_height = @x_axis_label.nil? ? 0.0 : marker_caps_height + (LABEL_MARGIN * 2)
|
969
|
+
@raw_rows - graph_bottom_margin - x_axis_label_height
|
889
970
|
end
|
890
971
|
|
891
972
|
def truncate_label_text(text)
|
@@ -919,7 +1000,7 @@ module Gruff
|
|
919
1000
|
else
|
920
1001
|
value.to_s
|
921
1002
|
end
|
922
|
-
elsif (@spread
|
1003
|
+
elsif (@spread % (marker_count == 0 ? 1 : marker_count) == 0) || !@y_axis_increment.nil?
|
923
1004
|
value.to_i.to_s
|
924
1005
|
elsif @spread > 10.0
|
925
1006
|
sprintf('%0i', value)
|
@@ -952,44 +1033,31 @@ module Gruff
|
|
952
1033
|
end
|
953
1034
|
|
954
1035
|
def calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
|
955
|
-
|
956
|
-
|
1036
|
+
label_widths = [[]]
|
1037
|
+
label_lines = [[]]
|
957
1038
|
legend_labels.each do |label|
|
958
1039
|
width = calculate_width(@legend_font, label)
|
959
|
-
label_width = width + legend_square_width * 2.7
|
1040
|
+
label_width = width + (legend_square_width * 2.7)
|
960
1041
|
label_widths.last.push label_width
|
1042
|
+
label_lines.last.push label
|
961
1043
|
|
962
1044
|
if label_widths.last.sum > (@raw_columns * 0.9)
|
963
1045
|
label_widths.push [label_widths.last.pop]
|
1046
|
+
label_lines.push [label_lines.last.pop]
|
964
1047
|
end
|
965
1048
|
end
|
966
1049
|
|
967
|
-
label_widths
|
1050
|
+
label_widths.map(&:sum).zip(label_lines)
|
968
1051
|
end
|
969
1052
|
|
970
1053
|
def calculate_legend_height
|
971
1054
|
return 0.0 if @hide_legend
|
972
1055
|
|
973
1056
|
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
|
1057
|
+
legend_label_lines = calculate_legend_label_widths_for_each_line(legend_labels, @legend_box_size)
|
1058
|
+
line_height = [legend_caps_height, @legend_box_size].max
|
991
1059
|
|
992
|
-
|
1060
|
+
(line_height * legend_label_lines.count) + (@legend_margin * (legend_label_lines.count - 1))
|
993
1061
|
end
|
994
1062
|
|
995
1063
|
# Returns the height of the capital letter 'X' for the current font and
|
@@ -998,20 +1066,41 @@ module Gruff
|
|
998
1066
|
# Not scaled since it deals with dimensions that the regular scaling will
|
999
1067
|
# handle.
|
1000
1068
|
def calculate_caps_height(font)
|
1001
|
-
|
1002
|
-
|
1069
|
+
calculate_height(font, 'X')
|
1070
|
+
end
|
1071
|
+
|
1072
|
+
def calculate_labels_height(font)
|
1073
|
+
@labels.values.map { |label| calculate_height(font, label, rotation: @label_rotation) }.max || marker_caps_height
|
1074
|
+
end
|
1075
|
+
|
1076
|
+
# Returns the height of a string at this point size.
|
1077
|
+
#
|
1078
|
+
# Not scaled since it deals with dimensions that the regular scaling will
|
1079
|
+
# handle.
|
1080
|
+
def calculate_height(font, text, rotation: 0)
|
1081
|
+
text = text.to_s
|
1082
|
+
return 0 if text.empty?
|
1083
|
+
|
1084
|
+
metrics = text_metrics(font, text, rotation: rotation)
|
1085
|
+
# Calculate manually because it does not return the height after rotation.
|
1086
|
+
(metrics.width * Math.sin(deg2rad(rotation))).abs + (metrics.height * Math.cos(deg2rad(rotation))).abs
|
1003
1087
|
end
|
1004
1088
|
|
1005
1089
|
# Returns the width of a string at this point size.
|
1006
1090
|
#
|
1007
1091
|
# Not scaled since it deals with dimensions that the regular
|
1008
1092
|
# scaling will handle.
|
1009
|
-
def calculate_width(font, text)
|
1093
|
+
def calculate_width(font, text, rotation: 0)
|
1010
1094
|
text = text.to_s
|
1011
1095
|
return 0 if text.empty?
|
1012
1096
|
|
1013
|
-
metrics =
|
1014
|
-
|
1097
|
+
metrics = text_metrics(font, text, rotation: rotation)
|
1098
|
+
# Calculate manually because it does not return the width after rotation.
|
1099
|
+
(metrics.width * Math.cos(deg2rad(rotation))).abs - (metrics.height * Math.sin(deg2rad(rotation))).abs
|
1100
|
+
end
|
1101
|
+
|
1102
|
+
def text_metrics(font, text, rotation: 0)
|
1103
|
+
Gruff::Renderer::Text.new(renderer, text, font: font, rotation: rotation).metrics
|
1015
1104
|
end
|
1016
1105
|
|
1017
1106
|
def calculate_increment
|
@@ -1019,17 +1108,39 @@ module Gruff
|
|
1019
1108
|
# Try to use a number of horizontal lines that will come out even.
|
1020
1109
|
#
|
1021
1110
|
# TODO Do the same for larger numbers...100, 75, 50, 25
|
1022
|
-
@increment = @spread > 0 && marker_count > 0 ? significant(@spread / marker_count) : 1
|
1111
|
+
@increment = @spread > 0 && marker_count > 0 ? significant(@spread / marker_count) : 1.0
|
1023
1112
|
else
|
1024
1113
|
# TODO: Make this work for negative values
|
1025
1114
|
self.marker_count = (@spread / @y_axis_increment).to_i
|
1026
|
-
@increment = @y_axis_increment
|
1115
|
+
@increment = @y_axis_increment.to_f
|
1027
1116
|
end
|
1028
1117
|
end
|
1029
1118
|
|
1030
|
-
|
1119
|
+
def calculate_label_offset(font, label, margin, rotation)
|
1120
|
+
width = calculate_width(font, label, rotation: rotation)
|
1121
|
+
height = calculate_height(font, label, rotation: rotation)
|
1122
|
+
x_offset = begin
|
1123
|
+
case rotation
|
1124
|
+
when 0
|
1125
|
+
0
|
1126
|
+
when 0..45
|
1127
|
+
width / 2.0
|
1128
|
+
when -45..0
|
1129
|
+
-(width / 2.0)
|
1130
|
+
end
|
1131
|
+
end
|
1132
|
+
y_offset = (height / 2.0) > margin ? (height / 2.0) : margin
|
1133
|
+
|
1134
|
+
[x_offset, y_offset]
|
1135
|
+
end
|
1136
|
+
|
1137
|
+
# Used for degree <=> radian conversions
|
1031
1138
|
def deg2rad(angle)
|
1032
|
-
angle *
|
1139
|
+
(angle * Math::PI) / 180.0
|
1140
|
+
end
|
1141
|
+
|
1142
|
+
def rad2deg(angle)
|
1143
|
+
(angle / Math::PI) * 180.0
|
1033
1144
|
end
|
1034
1145
|
end
|
1035
1146
|
|