gruff 0.14.0-java → 0.17.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 +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 +17 -16
- 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
|
|