gruff 0.15.0-java → 0.16.0-java
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 +17 -4
- data/.rubocop.yml +0 -6
- data/CHANGELOG.md +26 -0
- data/README.md +10 -3
- data/gruff.gemspec +1 -1
- data/lib/gruff/accumulator_bar.rb +1 -1
- data/lib/gruff/area.rb +4 -4
- data/lib/gruff/bar.rb +28 -9
- data/lib/gruff/base.rb +109 -118
- data/lib/gruff/bezier.rb +2 -2
- data/lib/gruff/box_plot.rb +174 -0
- data/lib/gruff/bullet.rb +5 -5
- data/lib/gruff/candlestick.rb +112 -0
- data/lib/gruff/dot.rb +10 -10
- data/lib/gruff/font.rb +3 -0
- data/lib/gruff/helper/bar_conversion.rb +5 -5
- data/lib/gruff/helper/bar_value_label.rb +27 -24
- data/lib/gruff/helper/stacked_mixin.rb +3 -1
- data/lib/gruff/histogram.rb +1 -1
- data/lib/gruff/line.rb +48 -42
- data/lib/gruff/mini/legend.rb +4 -4
- data/lib/gruff/net.rb +7 -7
- data/lib/gruff/patch/string.rb +1 -0
- data/lib/gruff/pie.rb +20 -11
- data/lib/gruff/renderer/dash_line.rb +3 -2
- data/lib/gruff/renderer/dot.rb +3 -1
- data/lib/gruff/renderer/line.rb +1 -3
- data/lib/gruff/renderer/rectangle.rb +6 -2
- data/lib/gruff/scatter.rb +52 -46
- data/lib/gruff/side_bar.rb +43 -14
- data/lib/gruff/side_stacked_bar.rb +20 -27
- data/lib/gruff/spider.rb +46 -13
- data/lib/gruff/stacked_area.rb +11 -6
- data/lib/gruff/stacked_bar.rb +36 -14
- data/lib/gruff/version.rb +1 -1
- data/lib/gruff.rb +9 -9
- metadata +4 -4
- data/lib/gruff/scene.rb +0 -208
- data/lib/gruff/store/custom_data.rb +0 -36
data/lib/gruff/base.rb
CHANGED
@@ -54,20 +54,18 @@ module Gruff
|
|
54
54
|
# { 0 => 2005, 3 => 2006, 5 => 2007, 7 => 2008 }
|
55
55
|
attr_writer :labels
|
56
56
|
|
57
|
-
# Used internally for spacing.
|
58
|
-
#
|
59
|
-
# By default, labels are centered over the point they represent.
|
60
|
-
attr_writer :center_labels_over_point
|
61
|
-
|
62
|
-
# Used internally for horizontal graph types. Default is +false+.
|
63
|
-
attr_writer :has_left_labels
|
64
|
-
|
65
57
|
# Set a label for the bottom of the graph.
|
66
58
|
attr_writer :x_axis_label
|
67
59
|
|
68
60
|
# Set a label for the left side of the graph.
|
69
61
|
attr_writer :y_axis_label
|
70
62
|
|
63
|
+
# Allow passing lambda to format labels for x axis.
|
64
|
+
attr_writer :x_axis_label_format
|
65
|
+
|
66
|
+
# Allow passing lambda to format labels for y axis.
|
67
|
+
attr_writer :y_axis_label_format
|
68
|
+
|
71
69
|
# Set increment of the vertical marking lines.
|
72
70
|
attr_writer :x_axis_increment
|
73
71
|
|
@@ -135,12 +133,6 @@ module Gruff
|
|
135
133
|
# Will be scaled down if graph is smaller than 800px wide.
|
136
134
|
attr_writer :legend_box_size
|
137
135
|
|
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
136
|
# If one numerical argument is given, the graph is drawn at 4/3 ratio
|
145
137
|
# according to the given width (+800+ results in 800x600, +400+ gives 400x300,
|
146
138
|
# etc.).
|
@@ -159,6 +151,9 @@ module Gruff
|
|
159
151
|
@columns.freeze
|
160
152
|
@rows.freeze
|
161
153
|
|
154
|
+
@has_left_labels = false
|
155
|
+
@center_labels_over_point = true
|
156
|
+
|
162
157
|
initialize_graph_scale
|
163
158
|
initialize_attributes
|
164
159
|
initialize_store
|
@@ -209,8 +204,6 @@ module Gruff
|
|
209
204
|
@no_data_message = 'No Data'
|
210
205
|
|
211
206
|
@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
207
|
@label_stagger_height = 0
|
215
208
|
@label_max_size = 0
|
216
209
|
@label_truncation_style = :absolute
|
@@ -429,7 +422,7 @@ module Gruff
|
|
429
422
|
#
|
430
423
|
# Set it after you have given all your data to the graph object.
|
431
424
|
def minimum_value
|
432
|
-
@minimum_value || store.min
|
425
|
+
(@minimum_value || store.min).to_f
|
433
426
|
end
|
434
427
|
attr_writer :minimum_value
|
435
428
|
|
@@ -439,7 +432,7 @@ module Gruff
|
|
439
432
|
# If you use this, you must set it after you have given all your data to
|
440
433
|
# the graph object.
|
441
434
|
def maximum_value
|
442
|
-
@maximum_value || store.max
|
435
|
+
(@maximum_value || store.max).to_f
|
443
436
|
end
|
444
437
|
attr_writer :maximum_value
|
445
438
|
|
@@ -512,8 +505,8 @@ module Gruff
|
|
512
505
|
# Perform data manipulation before calculating chart measurements
|
513
506
|
def setup_data # :nodoc:
|
514
507
|
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
|
508
|
+
self.maximum_value = [@y_axis_increment, maximum_value, (maximum_value / @y_axis_increment).round * @y_axis_increment].max
|
509
|
+
self.minimum_value = [minimum_value, (minimum_value / @y_axis_increment).round * @y_axis_increment].min
|
517
510
|
end
|
518
511
|
end
|
519
512
|
|
@@ -552,7 +545,7 @@ module Gruff
|
|
552
545
|
@marker_count ||= begin
|
553
546
|
count = nil
|
554
547
|
(3..7).each do |lines|
|
555
|
-
if @spread
|
548
|
+
if @spread % lines == 0.0
|
556
549
|
count = lines and break
|
557
550
|
end
|
558
551
|
end
|
@@ -565,8 +558,8 @@ module Gruff
|
|
565
558
|
store.normalize(minimum: minimum_value, spread: @spread)
|
566
559
|
end
|
567
560
|
|
568
|
-
def calculate_spread
|
569
|
-
@spread = maximum_value
|
561
|
+
def calculate_spread
|
562
|
+
@spread = maximum_value - minimum_value
|
570
563
|
@spread = @spread > 0 ? @spread : 1
|
571
564
|
end
|
572
565
|
|
@@ -579,28 +572,24 @@ module Gruff
|
|
579
572
|
end
|
580
573
|
|
581
574
|
def hide_left_label_area?
|
582
|
-
@hide_line_markers
|
575
|
+
@hide_line_markers && @y_axis_label.nil?
|
583
576
|
end
|
584
577
|
|
585
578
|
def hide_bottom_label_area?
|
586
|
-
@hide_line_markers
|
579
|
+
@hide_line_markers && @x_axis_label.nil?
|
587
580
|
end
|
588
581
|
|
589
582
|
##
|
590
583
|
# Calculates size of drawable area, general font dimensions, etc.
|
591
584
|
|
592
585
|
def setup_graph_measurements
|
593
|
-
@marker_caps_height = setup_marker_caps_height
|
594
|
-
@title_caps_height = setup_title_caps_height
|
595
|
-
@legend_caps_height = setup_legend_caps_height
|
596
|
-
|
597
586
|
margin_on_right = graph_right_margin
|
598
587
|
@graph_right = @raw_columns - margin_on_right
|
599
588
|
@graph_left = setup_left_margin
|
600
589
|
@graph_top = setup_top_margin
|
601
590
|
@graph_bottom = setup_bottom_margin
|
602
591
|
|
603
|
-
@graph_width = @
|
592
|
+
@graph_width = @graph_right - @graph_left
|
604
593
|
@graph_height = @graph_bottom - @graph_top
|
605
594
|
end
|
606
595
|
|
@@ -610,9 +599,8 @@ module Gruff
|
|
610
599
|
# X Axis
|
611
600
|
# Centered vertically and horizontally by setting the
|
612
601
|
# height to 1.0 and the width to the width of the graph.
|
613
|
-
x_axis_label_y_coordinate = @graph_bottom + LABEL_MARGIN +
|
602
|
+
x_axis_label_y_coordinate = @graph_bottom + (LABEL_MARGIN * 2) + marker_caps_height
|
614
603
|
|
615
|
-
# TODO: Center between graph area
|
616
604
|
text_renderer = Gruff::Renderer::Text.new(renderer, @x_axis_label, font: @marker_font)
|
617
605
|
text_renderer.add_to_render_queue(@raw_columns, 1.0, 0.0, x_axis_label_y_coordinate)
|
618
606
|
end
|
@@ -620,7 +608,7 @@ module Gruff
|
|
620
608
|
if @y_axis_label
|
621
609
|
# Y Axis, rotated vertically
|
622
610
|
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 +
|
611
|
+
text_renderer.add_to_render_queue(1.0, @raw_rows, @left_margin + (marker_caps_height / 2.0), 0.0, Magick::CenterGravity)
|
624
612
|
end
|
625
613
|
end
|
626
614
|
|
@@ -628,17 +616,17 @@ module Gruff
|
|
628
616
|
def draw_line_markers
|
629
617
|
return if @hide_line_markers
|
630
618
|
|
631
|
-
increment_scaled = @graph_height
|
619
|
+
increment_scaled = @graph_height / (@spread / @increment)
|
632
620
|
|
633
621
|
# Draw horizontal line markers and annotate with numbers
|
634
622
|
(0..marker_count).each do |index|
|
635
|
-
y = @graph_top + @graph_height - index
|
623
|
+
y = @graph_top + @graph_height - (index * increment_scaled)
|
636
624
|
|
637
|
-
|
638
|
-
|
625
|
+
Gruff::Renderer::Line.new(renderer, color: @marker_color).render(@graph_left, y, @graph_right, y)
|
626
|
+
Gruff::Renderer::Line.new(renderer, color: @marker_shadow_color).render(@graph_left, y + 1, @graph_right, y + 1) if @marker_shadow_color
|
639
627
|
|
640
628
|
unless @hide_line_numbers
|
641
|
-
marker_label = BigDecimal(index.to_s) * BigDecimal(@increment.to_s) + BigDecimal(minimum_value.to_s)
|
629
|
+
marker_label = (BigDecimal(index.to_s) * BigDecimal(@increment.to_s)) + BigDecimal(minimum_value.to_s)
|
642
630
|
label = y_axis_label(marker_label, @increment)
|
643
631
|
text_renderer = Gruff::Renderer::Text.new(renderer, label, font: @marker_font)
|
644
632
|
text_renderer.add_to_render_queue(@graph_left - LABEL_MARGIN, 1.0, 0.0, y, Magick::EastGravity)
|
@@ -658,45 +646,46 @@ module Gruff
|
|
658
646
|
|
659
647
|
legend_labels = store.data.map(&:label)
|
660
648
|
legend_square_width = @legend_box_size # small square with color of this item
|
661
|
-
|
649
|
+
legend_label_lines = calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
|
650
|
+
line_height = [legend_caps_height, legend_square_width].max + @legend_margin
|
662
651
|
|
663
|
-
current_x_offset = center(label_widths.first.sum)
|
664
652
|
current_y_offset = begin
|
665
653
|
if @legend_at_bottom
|
666
|
-
@graph_bottom + @legend_margin +
|
654
|
+
@graph_bottom + @legend_margin + legend_caps_height + LABEL_MARGIN + (@x_axis_label ? (LABEL_MARGIN * 2) + marker_caps_height : 0)
|
667
655
|
else
|
668
|
-
hide_title? ? @top_margin + @title_margin : @top_margin + @title_margin +
|
656
|
+
hide_title? ? @top_margin + @title_margin : @top_margin + @title_margin + title_caps_height
|
669
657
|
end
|
670
658
|
end
|
671
659
|
|
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
|
660
|
+
index = 0
|
661
|
+
legend_label_lines.each do |(legend_labels_width, legend_labels_line)|
|
662
|
+
current_x_offset = center(legend_labels_width)
|
663
|
+
|
664
|
+
legend_labels_line.each do |legend_label|
|
665
|
+
unless legend_label.empty?
|
666
|
+
legend_label_width = calculate_width(@legend_font, legend_label)
|
667
|
+
|
668
|
+
# Draw label
|
669
|
+
text_renderer = Gruff::Renderer::Text.new(renderer, legend_label, font: @legend_font)
|
670
|
+
text_renderer.add_to_render_queue(legend_label_width,
|
671
|
+
legend_square_width,
|
672
|
+
current_x_offset + (legend_square_width * 1.7),
|
673
|
+
current_y_offset,
|
674
|
+
Magick::CenterGravity)
|
675
|
+
|
676
|
+
# Now draw box with color of this dataset
|
677
|
+
rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: store.data[index].color)
|
678
|
+
rect_renderer.render(current_x_offset,
|
679
|
+
current_y_offset,
|
680
|
+
current_x_offset + legend_square_width,
|
681
|
+
current_y_offset + legend_square_width)
|
682
|
+
|
683
|
+
current_x_offset += legend_label_width + (legend_square_width * 2.7)
|
698
684
|
end
|
685
|
+
index += 1
|
699
686
|
end
|
687
|
+
|
688
|
+
current_y_offset += line_height
|
700
689
|
end
|
701
690
|
end
|
702
691
|
|
@@ -704,7 +693,7 @@ module Gruff
|
|
704
693
|
def draw_title
|
705
694
|
return if hide_title?
|
706
695
|
|
707
|
-
metrics =
|
696
|
+
metrics = text_metrics(@title_font, @title)
|
708
697
|
if metrics.width > @raw_columns
|
709
698
|
@title_font.size = @title_font.size * (@raw_columns / metrics.width) * 0.95
|
710
699
|
end
|
@@ -714,19 +703,14 @@ module Gruff
|
|
714
703
|
end
|
715
704
|
|
716
705
|
# 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)
|
706
|
+
def draw_label(x_offset, index, gravity = Magick::NorthGravity, &block)
|
720
707
|
draw_unique_label(index) do
|
721
708
|
y_offset = @graph_bottom + LABEL_MARGIN
|
722
|
-
|
723
|
-
# TESTME
|
724
|
-
# FIXME: Consider chart types other than bar
|
725
|
-
# TODO: See if index.odd? is the best strategy
|
726
709
|
y_offset += @label_stagger_height if index.odd?
|
727
710
|
|
728
711
|
if x_offset >= @graph_left && x_offset <= @graph_right
|
729
712
|
draw_label_at(1.0, 1.0, x_offset, y_offset, @labels[index], gravity)
|
713
|
+
yield if block
|
730
714
|
end
|
731
715
|
end
|
732
716
|
end
|
@@ -748,11 +732,10 @@ module Gruff
|
|
748
732
|
end
|
749
733
|
|
750
734
|
# Draws the data value over the data point in bar graphs
|
751
|
-
def draw_value_label(x_offset, y_offset, data_point)
|
735
|
+
def draw_value_label(width, height, x_offset, y_offset, data_point, gravity = Magick::CenterGravity)
|
752
736
|
return if @hide_line_markers
|
753
737
|
|
754
|
-
|
755
|
-
text_renderer.add_to_render_queue(1.0, 1.0, x_offset, y_offset)
|
738
|
+
draw_label_at(width, height, x_offset, y_offset, data_point, gravity)
|
756
739
|
end
|
757
740
|
|
758
741
|
# Shows an error message because you have no data.
|
@@ -829,15 +812,15 @@ module Gruff
|
|
829
812
|
|
830
813
|
private
|
831
814
|
|
832
|
-
def
|
815
|
+
def marker_caps_height
|
833
816
|
hide_bottom_label_area? ? 0 : calculate_caps_height(@marker_font)
|
834
817
|
end
|
835
818
|
|
836
|
-
def
|
819
|
+
def title_caps_height
|
837
820
|
hide_title? ? 0 : calculate_caps_height(@title_font) * @title.lines.to_a.size
|
838
821
|
end
|
839
822
|
|
840
|
-
def
|
823
|
+
def legend_caps_height
|
841
824
|
@hide_legend ? 0 : calculate_caps_height(@legend_font)
|
842
825
|
end
|
843
826
|
|
@@ -849,7 +832,11 @@ module Gruff
|
|
849
832
|
# Make space for half the width of the rightmost column label.
|
850
833
|
# Might be greater than the number of columns if between-style bar markers are used.
|
851
834
|
last_label = @labels.keys.max.to_i
|
852
|
-
last_label >= (column_count - 1) && @center_labels_over_point
|
835
|
+
if last_label >= (column_count - 1) && @center_labels_over_point
|
836
|
+
calculate_width(@marker_font, truncate_label_text(@labels[last_label])) / 2.0
|
837
|
+
else
|
838
|
+
0
|
839
|
+
end
|
853
840
|
end
|
854
841
|
|
855
842
|
def setup_left_margin
|
@@ -859,32 +846,30 @@ module Gruff
|
|
859
846
|
if @has_left_labels
|
860
847
|
@labels.values.reduce('') { |value, memo| value.to_s.length > memo.to_s.length ? value : memo }
|
861
848
|
else
|
862
|
-
y_axis_label(maximum_value
|
849
|
+
y_axis_label(maximum_value, @increment)
|
863
850
|
end
|
864
851
|
end
|
865
852
|
longest_left_label_width = calculate_width(@marker_font, truncate_label_text(text))
|
866
|
-
longest_left_label_width *= 1.25 if @has_left_labels
|
867
853
|
|
868
854
|
# Shift graph if left line numbers are hidden
|
869
|
-
line_number_width =
|
855
|
+
line_number_width = !@has_left_labels && (@hide_line_markers || @hide_line_numbers) ? 0.0 : (longest_left_label_width + LABEL_MARGIN)
|
870
856
|
|
871
|
-
@left_margin + line_number_width + (@y_axis_label.nil? ? 0.0 :
|
857
|
+
@left_margin + line_number_width + (@y_axis_label.nil? ? 0.0 : marker_caps_height + (LABEL_MARGIN * 2))
|
872
858
|
end
|
873
859
|
|
874
860
|
def setup_top_margin
|
875
861
|
# When @hide title, leave a title_margin space for aesthetics.
|
876
862
|
# Same with @hide_legend
|
877
863
|
@top_margin +
|
878
|
-
(hide_title? ? @title_margin :
|
864
|
+
(hide_title? ? @title_margin : title_caps_height + @title_margin) +
|
879
865
|
(@hide_legend || @legend_at_bottom ? @legend_margin : calculate_legend_height + @legend_margin)
|
880
866
|
end
|
881
867
|
|
882
868
|
def setup_bottom_margin
|
883
|
-
graph_bottom_margin = hide_bottom_label_area? ? @bottom_margin : @bottom_margin +
|
869
|
+
graph_bottom_margin = hide_bottom_label_area? ? @bottom_margin : @bottom_margin + marker_caps_height + LABEL_MARGIN
|
884
870
|
graph_bottom_margin += (calculate_legend_height + @legend_margin) if @legend_at_bottom
|
885
871
|
|
886
|
-
x_axis_label_height = @x_axis_label.nil? ? 0.0 :
|
887
|
-
# FIXME: Consider chart types other than bar
|
872
|
+
x_axis_label_height = @x_axis_label.nil? ? 0.0 : marker_caps_height + (LABEL_MARGIN * 2)
|
888
873
|
@raw_rows - graph_bottom_margin - x_axis_label_height - @label_stagger_height
|
889
874
|
end
|
890
875
|
|
@@ -919,7 +904,7 @@ module Gruff
|
|
919
904
|
else
|
920
905
|
value.to_s
|
921
906
|
end
|
922
|
-
elsif (@spread
|
907
|
+
elsif (@spread % (marker_count == 0 ? 1 : marker_count) == 0) || !@y_axis_increment.nil?
|
923
908
|
value.to_i.to_s
|
924
909
|
elsif @spread > 10.0
|
925
910
|
sprintf('%0i', value)
|
@@ -952,44 +937,31 @@ module Gruff
|
|
952
937
|
end
|
953
938
|
|
954
939
|
def calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
|
955
|
-
|
956
|
-
|
940
|
+
label_widths = [[]]
|
941
|
+
label_lines = [[]]
|
957
942
|
legend_labels.each do |label|
|
958
943
|
width = calculate_width(@legend_font, label)
|
959
|
-
label_width = width + legend_square_width * 2.7
|
944
|
+
label_width = width + (legend_square_width * 2.7)
|
960
945
|
label_widths.last.push label_width
|
946
|
+
label_lines.last.push label
|
961
947
|
|
962
948
|
if label_widths.last.sum > (@raw_columns * 0.9)
|
963
949
|
label_widths.push [label_widths.last.pop]
|
950
|
+
label_lines.push [label_lines.last.pop]
|
964
951
|
end
|
965
952
|
end
|
966
953
|
|
967
|
-
label_widths
|
954
|
+
label_widths.map(&:sum).zip(label_lines)
|
968
955
|
end
|
969
956
|
|
970
957
|
def calculate_legend_height
|
971
958
|
return 0.0 if @hide_legend
|
972
959
|
|
973
960
|
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
|
961
|
+
legend_label_lines = calculate_legend_label_widths_for_each_line(legend_labels, @legend_box_size)
|
962
|
+
line_height = [legend_caps_height, @legend_box_size].max
|
991
963
|
|
992
|
-
|
964
|
+
(line_height * legend_label_lines.count) + (@legend_margin * (legend_label_lines.count - 1))
|
993
965
|
end
|
994
966
|
|
995
967
|
# Returns the height of the capital letter 'X' for the current font and
|
@@ -998,7 +970,18 @@ module Gruff
|
|
998
970
|
# Not scaled since it deals with dimensions that the regular scaling will
|
999
971
|
# handle.
|
1000
972
|
def calculate_caps_height(font)
|
1001
|
-
|
973
|
+
calculate_height(font, 'X')
|
974
|
+
end
|
975
|
+
|
976
|
+
# Returns the height of a string at this point size.
|
977
|
+
#
|
978
|
+
# Not scaled since it deals with dimensions that the regular scaling will
|
979
|
+
# handle.
|
980
|
+
def calculate_height(font, text)
|
981
|
+
text = text.to_s
|
982
|
+
return 0 if text.empty?
|
983
|
+
|
984
|
+
metrics = text_metrics(font, text)
|
1002
985
|
metrics.height
|
1003
986
|
end
|
1004
987
|
|
@@ -1010,10 +993,14 @@ module Gruff
|
|
1010
993
|
text = text.to_s
|
1011
994
|
return 0 if text.empty?
|
1012
995
|
|
1013
|
-
metrics =
|
996
|
+
metrics = text_metrics(font, text)
|
1014
997
|
metrics.width
|
1015
998
|
end
|
1016
999
|
|
1000
|
+
def text_metrics(font, text)
|
1001
|
+
Gruff::Renderer::Text.new(renderer, text, font: font).metrics
|
1002
|
+
end
|
1003
|
+
|
1017
1004
|
def calculate_increment
|
1018
1005
|
if @y_axis_increment.nil?
|
1019
1006
|
# Try to use a number of horizontal lines that will come out even.
|
@@ -1027,9 +1014,13 @@ module Gruff
|
|
1027
1014
|
end
|
1028
1015
|
end
|
1029
1016
|
|
1030
|
-
# Used for degree
|
1017
|
+
# Used for degree <=> radian conversions
|
1031
1018
|
def deg2rad(angle)
|
1032
|
-
angle *
|
1019
|
+
(angle * Math::PI) / 180.0
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
def rad2deg(angle)
|
1023
|
+
(angle / Math::PI) * 180.0
|
1033
1024
|
end
|
1034
1025
|
end
|
1035
1026
|
|
data/lib/gruff/bezier.rb
CHANGED
@@ -22,7 +22,7 @@ class Gruff::Bezier < Gruff::Base
|
|
22
22
|
private
|
23
23
|
|
24
24
|
def draw_graph
|
25
|
-
x_increment = @graph_width / (column_count - 1)
|
25
|
+
x_increment = @graph_width / (column_count - 1)
|
26
26
|
|
27
27
|
store.norm_data.each do |data_row|
|
28
28
|
poly_points = []
|
@@ -30,7 +30,7 @@ private
|
|
30
30
|
data_row[1].each_with_index do |data_point, index|
|
31
31
|
# Use incremented x and scaled y
|
32
32
|
new_x = @graph_left + (x_increment * index)
|
33
|
-
new_y = @graph_top + (@graph_height - data_point * @graph_height)
|
33
|
+
new_y = @graph_top + (@graph_height - (data_point * @graph_height))
|
34
34
|
|
35
35
|
if index == 0 && RUBY_PLATFORM != 'java'
|
36
36
|
poly_points << new_x
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Here's how to set up a Gruff::BoxPlot.
|
5
|
+
#
|
6
|
+
# g = Gruff::BoxPlot.new
|
7
|
+
# g.data "A", [2, 3, 5, 6, 8, 10, 11, 15, 17, 20, 28, 29, 33, 34, 45, 46, 49, 61]
|
8
|
+
# g.data "B", [3, 4, 34, 35, 38, 39, 45, 60, 61, 69, 80, 130]
|
9
|
+
# g.data "C", [4, 40, 41, 46, 57, 64, 77, 76, 79, 78, 99, 153]
|
10
|
+
# g.write("box_plot.png")
|
11
|
+
#
|
12
|
+
class Gruff::BoxPlot < Gruff::Base
|
13
|
+
# Specifies the filling opacity in area graph. Default is +0.2+.
|
14
|
+
attr_writer :fill_opacity
|
15
|
+
|
16
|
+
# Specifies the stroke width in line. Default is +3.0+.
|
17
|
+
attr_writer :stroke_width
|
18
|
+
|
19
|
+
# Can be used to adjust the spaces between the bars.
|
20
|
+
# Accepts values between 0.00 and 1.00 where 0.00 means no spacing at all
|
21
|
+
# and 1 means that each bars' width is nearly 0 (so each bar is a simple
|
22
|
+
# line with no x dimension).
|
23
|
+
#
|
24
|
+
# Default value is +0.8+.
|
25
|
+
def spacing_factor=(space_percent)
|
26
|
+
raise ArgumentError, 'spacing_factor must be between 0.00 and 1.00' unless (space_percent >= 0) && (space_percent <= 1)
|
27
|
+
|
28
|
+
@spacing_factor = (1 - space_percent)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def initialize_attributes
|
34
|
+
super
|
35
|
+
@fill_opacity = 0.2
|
36
|
+
@stroke_width = 3.0
|
37
|
+
@spacing_factor = 0.8
|
38
|
+
end
|
39
|
+
|
40
|
+
def draw_graph
|
41
|
+
# Setup the BarConversion Object
|
42
|
+
conversion = Gruff::BarConversion.new(
|
43
|
+
top: @graph_top, bottom: @graph_bottom,
|
44
|
+
minimum_value: minimum_value, maximum_value: maximum_value, spread: @spread
|
45
|
+
)
|
46
|
+
|
47
|
+
width = (@graph_width - calculate_spacing) / normalized_boxes.size
|
48
|
+
bar_width = width * @spacing_factor
|
49
|
+
padding = width - bar_width
|
50
|
+
|
51
|
+
normalized_boxes.each_with_index do |box, index|
|
52
|
+
left_x = @graph_left + (width * index) + (padding / 2.0)
|
53
|
+
right_x = left_x + bar_width
|
54
|
+
center_x = (left_x + right_x) / 2.0
|
55
|
+
|
56
|
+
first_y, = conversion.get_top_bottom_scaled(box.first_quartile)
|
57
|
+
third_y, = conversion.get_top_bottom_scaled(box.third_quartile)
|
58
|
+
Gruff::Renderer::Rectangle.new(renderer, color: box.color, width: @stroke_width, opacity: @fill_opacity)
|
59
|
+
.render(left_x, first_y, right_x, third_y)
|
60
|
+
|
61
|
+
median_y, = conversion.get_top_bottom_scaled(box.median)
|
62
|
+
Gruff::Renderer::Line.new(renderer, color: box.color, width: @stroke_width * 2).render(left_x, median_y, right_x, median_y)
|
63
|
+
|
64
|
+
minmax_left_x = left_x + (bar_width / 4.0)
|
65
|
+
minmax_right_x = right_x - (bar_width / 4.0)
|
66
|
+
min_y, = conversion.get_top_bottom_scaled(box.lower_whisker)
|
67
|
+
Gruff::Renderer::Line.new(renderer, color: box.color, width: @stroke_width).render(minmax_left_x, min_y, minmax_right_x, min_y)
|
68
|
+
Gruff::Renderer::DashLine.new(renderer, color: box.color, width: @stroke_width, dasharray: [@stroke_width, @stroke_width * 2])
|
69
|
+
.render(center_x, min_y, center_x, first_y)
|
70
|
+
|
71
|
+
max_y, = conversion.get_top_bottom_scaled(box.upper_whisker)
|
72
|
+
Gruff::Renderer::Line.new(renderer, color: box.color, width: @stroke_width).render(minmax_left_x, max_y, minmax_right_x, max_y)
|
73
|
+
Gruff::Renderer::DashLine.new(renderer, color: box.color, width: @stroke_width, dasharray: [@stroke_width, @stroke_width * 2])
|
74
|
+
.render(center_x, max_y, center_x, third_y)
|
75
|
+
|
76
|
+
box.lower_outliers.each do |outlier|
|
77
|
+
outlier_y, = conversion.get_top_bottom_scaled(outlier)
|
78
|
+
Gruff::Renderer::Dot.new(renderer, :circle, color: box.color, opacity: @fill_opacity).render(center_x, outlier_y, @stroke_width * 2)
|
79
|
+
end
|
80
|
+
|
81
|
+
box.upper_outliers.each do |outlier|
|
82
|
+
outlier_y, = conversion.get_top_bottom_scaled(outlier)
|
83
|
+
Gruff::Renderer::Dot.new(renderer, :circle, color: box.color, opacity: @fill_opacity).render(center_x, outlier_y, @stroke_width * 2)
|
84
|
+
end
|
85
|
+
|
86
|
+
draw_label(center_x, index)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def normalized_boxes
|
91
|
+
@normalized_boxes ||= store.norm_data.map { |data| Gruff::BoxPlot::BoxData.new(data.label, data.points, data.color) }
|
92
|
+
end
|
93
|
+
|
94
|
+
def calculate_spacing
|
95
|
+
@scale * (column_count - 1)
|
96
|
+
end
|
97
|
+
|
98
|
+
# @private
|
99
|
+
class BoxData < Struct.new(:label, :points, :color)
|
100
|
+
def initialize(label, points, color)
|
101
|
+
super(label, points.sort, color)
|
102
|
+
end
|
103
|
+
|
104
|
+
def min
|
105
|
+
points.first || 0
|
106
|
+
end
|
107
|
+
|
108
|
+
def max
|
109
|
+
points.last || 0
|
110
|
+
end
|
111
|
+
|
112
|
+
def min_whisker
|
113
|
+
[min, first_quartile - (1.5 * interquartile_range)].max
|
114
|
+
end
|
115
|
+
|
116
|
+
def max_whisker
|
117
|
+
[max, third_quartile + (1.5 * interquartile_range)].min
|
118
|
+
end
|
119
|
+
|
120
|
+
def upper_whisker
|
121
|
+
max = max_whisker
|
122
|
+
points.select { |point| point <= max }.max
|
123
|
+
end
|
124
|
+
|
125
|
+
def lower_whisker
|
126
|
+
min = min_whisker
|
127
|
+
points.select { |point| point >= min }.min
|
128
|
+
end
|
129
|
+
|
130
|
+
def median
|
131
|
+
if points.size.zero?
|
132
|
+
0
|
133
|
+
elsif points.size.odd?
|
134
|
+
points[points.size / 2]
|
135
|
+
else
|
136
|
+
(points[points.size / 2] + points[(points.size / 2) - 1]) / 2.0
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def first_quartile
|
141
|
+
if points.size.zero?
|
142
|
+
0
|
143
|
+
elsif points.size.odd?
|
144
|
+
points[points.size / 4]
|
145
|
+
else
|
146
|
+
(points[points.size / 4] + points[(points.size / 4) - 1]) / 2.0
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def third_quartile
|
151
|
+
if points.size.zero?
|
152
|
+
0
|
153
|
+
elsif points.size.odd?
|
154
|
+
points[(points.size * 3) / 4]
|
155
|
+
else
|
156
|
+
(points[(points.size * 3) / 4] + points[((points.size * 3) / 4) - 1]) / 2.0
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def lower_outliers
|
161
|
+
min = lower_whisker
|
162
|
+
points.select { |point| point < min }
|
163
|
+
end
|
164
|
+
|
165
|
+
def upper_outliers
|
166
|
+
max = upper_whisker
|
167
|
+
points.select { |point| point > max }
|
168
|
+
end
|
169
|
+
|
170
|
+
def interquartile_range
|
171
|
+
third_quartile - first_quartile
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|