Skip to content

Commit b2ade4d

Browse files
committed
Floating legend
1 parent 5a66fad commit b2ade4d

375 files changed

Lines changed: 88 additions & 148 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

lib/gruff/bar.rb

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,6 @@ def hide_left_label_area?
9090
@hide_line_markers && @y_axis_label.nil?
9191
end
9292

93-
# @rbs return: bool
94-
def hide_bottom_label_area?
95-
hide_labels? && @x_axis_label.nil? && @legend_at_bottom == false
96-
end
97-
9893
# @rbs return: void
9994
def setup_graph_measurements
10095
super

lib/gruff/base.rb

Lines changed: 59 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ module Gruff
2323
# A common base class inherited from class of drawing a graph.
2424
class Base
2525
# Space around text elements. Mostly used for vertical spacing.
26-
LEGEND_MARGIN = 20.0
26+
LEGEND_MARGIN = 10.0
2727
TITLE_MARGIN = 20.0
2828
LABEL_MARGIN = 15.0
2929
DEFAULT_MARGIN = 20.0
@@ -48,9 +48,6 @@ class Base
4848
# Blank space below the title. Default is +20+.
4949
attr_writer :title_margin #: Float | Integer
5050

51-
# Blank space below the legend. Default is +20+.
52-
attr_writer :legend_margin #: Float | Integer
53-
5451
# Truncates labels if longer than max specified.
5552
attr_writer :label_max_size #: Float | Integer
5653

@@ -115,13 +112,16 @@ class Base
115112
# first. This does not affect the legend. Default is +false+.
116113
attr_writer :sorted_drawing #: bool
117114

118-
# Display the legend under the graph. Default is +false+.
119-
attr_writer :legend_at_bottom #: bool
115+
# Blank space below the legend. Default is +20+.
116+
attr_writer :legend_margin #: Float | Integer
117+
118+
# Set the vertical spacing between individual legend items. Default is +5.0+.
119+
attr_writer :legend_spacing #: Float | Integer
120120

121-
# Optionally set the size of the colored box by each item in the legend.
122-
# Default is +20.0+.
123-
#
124-
# Will be scaled down if graph is smaller than 800px wide.
121+
# Set the inner padding between the legend frame and its contents. Default is +7.0+.
122+
attr_writer :legend_padding #: Float | Integer
123+
124+
# Optionally set the size of the colored box by each item in the legend. Default is +20.0+.
125125
attr_writer :legend_box_size #: Float | Integer
126126

127127
# If one numerical argument is given, the graph is drawn at 4/3 ratio
@@ -190,19 +190,21 @@ def initialize_attributes
190190

191191
@title_font = Gruff::Font.new(size: 36.0, bold: true)
192192
@marker_font = Gruff::Font.new(size: 21.0)
193-
@legend_font = Gruff::Font.new(size: 20.0)
193+
@legend_font = Gruff::Font.new(size: 12.0)
194194
@no_data_font = Gruff::Font.new(size: 80.0)
195195

196196
@label_margin = LABEL_MARGIN
197197
@top_margin = @bottom_margin = @left_margin = @right_margin = DEFAULT_MARGIN
198-
@legend_margin = LEGEND_MARGIN
199198
@title_margin = TITLE_MARGIN
200199

201-
@legend_box_size = 20.0
200+
@legend_margin = LEGEND_MARGIN
201+
@legend_spacing = 5.0
202+
@legend_padding = 7.0
203+
@legend_box_size = 10.0
202204

203205
@no_data_message = 'No Data'
204206

205-
@hide_line_markers = @hide_legend = @hide_title = @hide_line_numbers = @legend_at_bottom = false
207+
@hide_line_markers = @hide_legend = @hide_title = @hide_line_numbers = false
206208
@label_max_size = 0
207209
@label_truncation_style = :absolute
208210
@label_rotation = 0
@@ -262,6 +264,13 @@ def label_stagger_height=(_value)
262264
warn '#label_stagger_height= is deprecated. It is no longer effective.'
263265
end
264266

267+
# Set the legend position to the bottom of the graph.
268+
# @deprecated
269+
# @rbs return: void
270+
def legend_at_bottom=(_value)
271+
warn '#legend_at_bottom= is deprecated. It is no longer effective.'
272+
end
273+
265274
# Set the large title of the graph displayed at the top.
266275
# You can draw a multi-line title by putting a line break in the string
267276
# or by setting an array as argument.
@@ -629,11 +638,11 @@ def draw
629638

630639
setup_drawing
631640

632-
draw_legend
633641
draw_line_markers
634642
draw_axis_labels
635643
draw_title
636644
draw_graph
645+
draw_legend
637646
end
638647

639648
protected
@@ -822,46 +831,42 @@ def center(size)
822831
def draw_legend
823832
return if @hide_legend
824833

825-
legend_labels = store.data.map(&:label)
826-
legend_square_width = @legend_box_size # small square with color of this item
827-
legend_label_lines = calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
828-
line_height = [legend_caps_height, legend_square_width].max + @legend_margin
834+
legend_labels = store.data.map(&:label).reject(&:empty?)
835+
return if legend_labels.empty?
829836

830-
current_y_offset = begin
831-
if @legend_at_bottom
832-
@graph_bottom + @legend_margin + labels_caps_height + @label_margin + (@x_axis_label ? (@label_margin * 2) + marker_caps_height : 0)
833-
else
834-
hide_title? ? @top_margin + @title_margin : @top_margin + @title_margin + title_caps_height
835-
end
836-
end
837+
line_height = [legend_caps_height, @legend_box_size].max + @legend_spacing
837838

838-
index = 0
839-
legend_label_lines.each do |(legend_labels_width, legend_labels_line)|
840-
current_x_offset = center(legend_labels_width)
841-
842-
legend_labels_line.each do |legend_label|
843-
unless legend_label.empty?
844-
legend_label_width = calculate_width(@legend_font, legend_label)
845-
846-
# Draw label
847-
text_renderer = Gruff::Renderer::Text.new(renderer, legend_label, font: @legend_font)
848-
text_renderer.add_to_render_queue(legend_label_width,
849-
legend_square_width,
850-
current_x_offset + (legend_square_width * 1.7),
851-
current_y_offset,
852-
Magick::CenterGravity)
853-
854-
# Now draw box with color of this dataset
855-
rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: store.data[index].color)
856-
rect_renderer.render(current_x_offset,
857-
current_y_offset,
858-
current_x_offset + legend_square_width,
859-
current_y_offset + legend_square_width)
860-
861-
current_x_offset += legend_label_width + (legend_square_width * 2.7)
862-
end
863-
index += 1
864-
end
839+
# Draw legend inside graph area, top-right corner, stacked vertically
840+
max_legend_label_width = legend_labels.map { |l| calculate_width(@legend_font, l) }.max || 0.0
841+
current_x_offset = @graph_right - @legend_margin - (@legend_box_size * 1.5) - max_legend_label_width
842+
current_y_offset = @graph_top + @legend_margin + @legend_spacing
843+
last_item_y_offset = current_y_offset + (line_height * (legend_labels.size - 1))
844+
845+
legend_items_end_x = @graph_right - @legend_margin
846+
legend_items_end_y = last_item_y_offset + @legend_box_size
847+
848+
# Draw legend frame around all items
849+
frame_renderer = Gruff::Renderer::Rectangle.new(renderer, color: @marker_color, width: 1.1, opacity: 0.2, round: true)
850+
frame_renderer.render(current_x_offset - @legend_padding,
851+
current_y_offset - @legend_padding,
852+
legend_items_end_x + @legend_padding,
853+
legend_items_end_y + @legend_padding)
854+
855+
legend_labels.each_with_index do |legend_label, index|
856+
legend_label_width = calculate_width(@legend_font, legend_label)
857+
858+
text_renderer = Gruff::Renderer::Text.new(renderer, legend_label, font: @legend_font)
859+
text_renderer.add_to_render_queue(legend_label_width,
860+
@legend_box_size,
861+
current_x_offset + (@legend_box_size * 1.5),
862+
current_y_offset,
863+
Magick::CenterGravity)
864+
865+
rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: store.data[index].color)
866+
rect_renderer.render(current_x_offset,
867+
current_y_offset,
868+
current_x_offset + @legend_box_size,
869+
current_y_offset + @legend_box_size)
865870

866871
current_y_offset += line_height
867872
end
@@ -1123,14 +1128,12 @@ def setup_top_margin
11231128
# When @hide title, leave a title_margin space for aesthetics.
11241129
# Same with @hide_legend
11251130
@top_margin +
1126-
(hide_title? ? @title_margin : title_caps_height + @title_margin) +
1127-
(@hide_legend || @legend_at_bottom ? @legend_margin : calculate_legend_height + @legend_margin)
1131+
(hide_title? ? @title_margin : title_caps_height + @title_margin)
11281132
end
11291133

11301134
# @rbs return: Float
11311135
def setup_bottom_margin
11321136
graph_bottom_margin = hide_bottom_label_area? ? @bottom_margin : @bottom_margin + labels_caps_height + @label_margin
1133-
graph_bottom_margin += (calculate_legend_height + @legend_margin) if @legend_at_bottom
11341137

11351138
x_axis_label_height = @x_axis_label.nil? ? 0.0 : marker_caps_height + (@label_margin * 2)
11361139
@raw_rows - graph_bottom_margin - x_axis_label_height
@@ -1211,42 +1214,6 @@ def y_axis_label(value, increment)
12111214
end
12121215
end
12131216

1214-
# @rbs legend_labels: Array[String]
1215-
# @rbs legend_square_width: Float | Integer
1216-
# @rbs return: Array[Array[Float | Integer]]
1217-
def calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
1218-
label_widths = [[]]
1219-
label_lines = [[]]
1220-
legend_labels.each do |label|
1221-
if label.empty?
1222-
label_width = 0.0
1223-
else
1224-
width = calculate_width(@legend_font, label)
1225-
label_width = width + (legend_square_width * 2.7)
1226-
end
1227-
label_widths.last.push label_width
1228-
label_lines.last.push label
1229-
1230-
if label_widths.last.sum > (@raw_columns * 0.9)
1231-
label_widths.push [label_widths.last.pop]
1232-
label_lines.push [label_lines.last.pop]
1233-
end
1234-
end
1235-
1236-
label_widths.map(&:sum).zip(label_lines)
1237-
end
1238-
1239-
# @rbs return: Float
1240-
def calculate_legend_height
1241-
return 0.0 if @hide_legend
1242-
1243-
legend_labels = store.data.map(&:label)
1244-
legend_label_lines = calculate_legend_label_widths_for_each_line(legend_labels, @legend_box_size)
1245-
line_height = [legend_caps_height, @legend_box_size].max
1246-
1247-
(line_height * legend_label_lines.count) + (@legend_margin * (legend_label_lines.count - 1))
1248-
end
1249-
12501217
# Returns the height of the capital letter 'X' for the current font and
12511218
# size.
12521219
#

lib/gruff/dot.rb

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,6 @@ def hide_left_label_area?
4545
hide_labels? && @y_axis_label.nil?
4646
end
4747

48-
# @rbs return: bool
49-
def hide_bottom_label_area?
50-
@hide_line_markers && @x_axis_label.nil? && @legend_at_bottom == false
51-
end
52-
5348
# @rbs return: void
5449
def draw_graph
5550
# Setup spacing.

lib/gruff/renderer/rectangle.rb

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ class Renderer::Rectangle
99
# @rbs color: String
1010
# @rbs width: Float | Integer
1111
# @rbs opacity: Float | Integer
12+
# @rbs round: bool
1213
# @rbs return: void
13-
def initialize(renderer, color: nil, width: 1.0, opacity: 1.0)
14+
def initialize(renderer, color: nil, width: 1.0, opacity: 1.0, round: false)
1415
@renderer = renderer
1516
@color = color
1617
@width = width
1718
@opacity = opacity
19+
@round = round
1820
end
1921

2022
# @rbs upper_left_x: Float | Integer
@@ -25,10 +27,17 @@ def initialize(renderer, color: nil, width: 1.0, opacity: 1.0)
2527
def render(upper_left_x, upper_left_y, lower_right_x, lower_right_y)
2628
@renderer.draw.push
2729
@renderer.draw.stroke_width(@width)
28-
@renderer.draw.stroke(@color) if @color && @width > 1.0
29-
@renderer.draw.fill_opacity(@opacity)
30-
@renderer.draw.fill(@color) if @color
31-
@renderer.draw.rectangle(upper_left_x, upper_left_y, lower_right_x, lower_right_y)
30+
if @color
31+
@renderer.draw.stroke(@color) if @width > 1.0
32+
@renderer.draw.fill_opacity(@opacity)
33+
@renderer.draw.fill(@color)
34+
end
35+
36+
if @round
37+
@renderer.draw.roundrectangle(upper_left_x, upper_left_y, lower_right_x, lower_right_y, 5, 5)
38+
else
39+
@renderer.draw.rectangle(upper_left_x, upper_left_y, lower_right_x, lower_right_y)
40+
end
3241
@renderer.draw.pop
3342
end
3443
end

lib/gruff/side_bar.rb

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,6 @@ def hide_left_label_area?
8383
hide_labels? && @y_axis_label.nil?
8484
end
8585

86-
# @rbs return: bool
87-
def hide_bottom_label_area?
88-
@hide_line_markers && @x_axis_label.nil? && @legend_at_bottom == false
89-
end
90-
9186
# @rbs return: void
9287
def setup_graph_measurements
9388
super

lib/gruff/stacked_bar.rb

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,6 @@ def hide_left_label_area?
134134
@hide_line_markers && @y_axis_label.nil?
135135
end
136136

137-
# @rbs return: bool
138-
def hide_bottom_label_area?
139-
hide_labels? && @x_axis_label.nil? && @legend_at_bottom == false
140-
end
141-
142137
# @rbs return: Proc
143138
def proc_text_metrics
144139
->(text) { text_metrics(@marker_font, text) }

sig/generated/gruff/bar.rbs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,6 @@ class Gruff::Bar < Gruff::Base
6262
# @rbs return: bool
6363
def hide_left_label_area?: () -> bool
6464

65-
# @rbs return: bool
66-
def hide_bottom_label_area?: () -> bool
67-
6865
# @rbs return: void
6966
def setup_graph_measurements: () -> void
7067

sig/generated/gruff/base.rbs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,20 @@ module Gruff
112112
# first. This does not affect the legend. Default is +false+.
113113
attr_writer sorted_drawing: bool
114114

115-
# Display the legend under the graph. Default is +false+.
116-
attr_writer legend_at_bottom: bool
117-
118115
# Optionally set the size of the colored box by each item in the legend.
119116
# Default is +20.0+.
120117
#
121118
# Will be scaled down if graph is smaller than 800px wide.
122119
attr_writer legend_box_size: Float | Integer
123120

121+
# Set the vertical spacing between individual legend items.
122+
# Default is +5.0+.
123+
attr_writer legend_spacing: Float | Integer
124+
125+
# Set the inner padding between the legend frame and its contents.
126+
# Default is +7.0+.
127+
attr_writer legend_padding: Float | Integer
128+
124129
# If one numerical argument is given, the graph is drawn at 4/3 ratio
125130
# according to the given width (+800+ results in 800x600, +400+ gives 400x300,
126131
# etc.).
@@ -181,6 +186,11 @@ module Gruff
181186
# @rbs return: void
182187
def label_stagger_height=: (untyped _value) -> void
183188

189+
# Set the legend position to the bottom of the graph.
190+
# @deprecated
191+
# @rbs return: void
192+
def legend_at_bottom=: (untyped _value) -> void
193+
184194
# Set the large title of the graph displayed at the top.
185195
# You can draw a multi-line title by putting a line break in the string
186196
# or by setting an array as argument.
@@ -657,14 +667,6 @@ module Gruff
657667
# @rbs return: String
658668
def y_axis_label: (Float | Integer | BigDecimal value, Float | Integer increment) -> String
659669

660-
# @rbs legend_labels: Array[String]
661-
# @rbs legend_square_width: Float | Integer
662-
# @rbs return: Array[Array[Float | Integer]]
663-
def calculate_legend_label_widths_for_each_line: (Array[String] legend_labels, Float | Integer legend_square_width) -> Array[Array[Float | Integer]]
664-
665-
# @rbs return: Float
666-
def calculate_legend_height: () -> Float
667-
668670
# Returns the height of the capital letter 'X' for the current font and
669671
# size.
670672
#

sig/generated/gruff/dot.rbs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@ class Gruff::Dot < Gruff::Base
3131
# @rbs return: bool
3232
def hide_left_label_area?: () -> bool
3333

34-
# @rbs return: bool
35-
def hide_bottom_label_area?: () -> bool
36-
3734
# @rbs return: void
3835
def draw_graph: () -> void
3936

0 commit comments

Comments
 (0)